Index: trunk/resources/data/validator/numeric.mapcss
===================================================================
--- trunk/resources/data/validator/numeric.mapcss	(revision 18756)
+++ trunk/resources/data/validator/numeric.mapcss	(revision 18757)
@@ -68,70 +68,4 @@
 }
 
-*[height][height =~ /^[0-9]+(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set height_meter_autofix;
-  fixAdd: concat("height=", get(regexp_match("([0-9.]+)( )*(.+)",tag("height")),1)," m");
-  assertMatch: "node height=6.78 meters";
-  assertMatch: "node height=5  metre";
-  assertMatch: "node height=2m";
-  assertNoMatch: "node height=2 m";
-  assertNoMatch: "node height=5";
-}
-*[height][height =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set height_foot_autofix;
-  fixAdd: concat("height=", get(regexp_match("([0-9.]+)( )*(.+)",tag("height")),1)," ft");
-  assertMatch: "node height=6.78 foot";
-  assertMatch: "node height=5  Feet";
-  assertMatch: "node height=2ft";
-  assertNoMatch: "node height=2 ft";
-  assertNoMatch: "node height=5";
-}
-*[height][height =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("height=", replace(tag("height"), ",", "."));
-  set height_separator_autofix;
-  assertMatch: "node height=5,5";
-  assertMatch: "node height=12,00";
-  assertMatch: "node height=12,5 ft";
-  assertNoMatch: "node height=12,000";
-  assertNoMatch: "node height=3,50,5";
-  assertNoMatch: "node height=3.5";
-  assertNoMatch: "node height=4";
-}
-
-*[maxheight][maxheight =~ /^[1-9][0-9]*(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxheight_meter_autofix;
-  fixAdd: concat("maxheight=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxheight")),1)," m");
-  assertMatch: "node maxheight=6.78 meters";
-  assertMatch: "node maxheight=5  metre";
-  assertMatch: "node maxheight=2m";
-  assertNoMatch: "node maxheight=2 m";
-  assertNoMatch: "node maxheight=5";
-}
-*[maxheight][maxheight =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxheight_foot_autofix;
-  fixAdd: concat("maxheight=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxheight")),1)," ft");
-  assertMatch: "node maxheight=6.78 foot";
-  assertMatch: "node maxheight=5  Feet";
-  assertMatch: "node maxheight=2ft";
-  assertNoMatch: "node maxheight=2 ft";
-  assertNoMatch: "node maxheight=5";
-}
-*[maxheight][maxheight =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("maxheight=", replace(tag("maxheight"), ",", "."));
-  set maxheight_separator_autofix;
-  assertMatch: "node maxheight=5,5";
-  assertMatch: "node maxheight=12,00";
-  assertMatch: "node maxheight=12,5 ft";
-  assertNoMatch: "node maxheight=12,000";
-  assertNoMatch: "node maxheight=3,50,5";
-  assertNoMatch: "node maxheight=3.5";
-  assertNoMatch: "node maxheight=4";
-}
-
 *[roof:height][roof:height =~ /^0*(\.0*)?( (m|ft))?$/][roof:shape=flat] {
   throwWarning: tr("{0} is unnecessary for {1}", "{0.tag}", "{2.tag}");
@@ -144,8 +78,33 @@
   assertNoMatch: "node roof:height=0 roof:shape=gabled";
 }
-*[roof:height][roof:height =~ /^[0-9]+(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/]!.zero_roof_height_flat {
+
+/*********************
+ * Begin Unit checks *
+ *********************/
+/* See https://wiki.openstreetmap.org/wiki/Map_features/Units */
+/* 1. Replace aliases to make the rest of the checks easier to implement */
+/* Distance measurements, note that these should look also match `,` separators to ensure that at least one error is matched */
+/* Meters; Note that we cannot assertMatch "2  m" since we replace the double space with a single space */
+*[height][height            =~ /^(?i)[0-9]+([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/],
+*[roof:height][roof:height  =~ /^(?i)[0-9]+([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/]!.zero_roof_height_flat,
+*[width][width              =~ /^(?i)[0-9]+([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/],
+*[maxwidth][maxwidth        =~ /^(?i)[0-9]+([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/],
+*[min_height][min_height    =~ /^(?i)-?[0-9]+([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/],
+*[maxheight][maxheight      =~ /^(?i)[1-9][0-9]*([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/],
+*[maxlength][maxlength      =~ /^(?i)[1-9][0-9]*([.,][0-9]+)?( *(metres?|meters?)|m| {2,}m)$/] {
+  set _unit_auto_fix;
   throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set roof_height_meter_autofix;
-  fixAdd: concat("roof:height=", get(regexp_match("([0-9.]+)( )*(.+)",tag("roof:height")),1)," m");
+  fixAdd: concat("{0.key}", "=", get(regexp_match("([0-9.,]+) *.+", "{0.value}"), 1), " m");
+  assertMatch: "node height=6.78 meters";
+  assertMatch: "node height=6,78 meters";
+  assertMatch: "node height=5  metre";
+  assertMatch: "node height=2m";
+  assertNoMatch: "node height=2 m";
+  assertNoMatch: "node height=5";
+  assertMatch: "node maxheight=6.78 meters";
+  assertMatch: "node maxheight=5  metre";
+  assertMatch: "node maxheight=2m";
+  assertNoMatch: "node maxheight=2 m";
+  assertNoMatch: "node maxheight=5";
   assertMatch: "node roof:height=6.78 meters";
   assertMatch: "node roof:height=5  metre";
@@ -153,32 +112,4 @@
   assertNoMatch: "node roof:height=2 m";
   assertNoMatch: "node roof:height=5";
-}
-*[roof:height][roof:height =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/]!.zero_roof_height_flat {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set roof_height_foot_autofix;
-  fixAdd: concat("roof:height=", get(regexp_match("([0-9.]+)( )*(.+)",tag("roof:height")),1)," ft");
-  assertMatch: "node roof:height=6.78 foot";
-  assertMatch: "node roof:height=5  Feet";
-  assertMatch: "node roof:height=2ft";
-  assertNoMatch: "node roof:height=2 ft";
-  assertNoMatch: "node roof:height=5";
-}
-*[roof:height][roof:height =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("roof:height=", replace(tag("roof:height"), ",", "."));
-  set roof_height_separator_autofix;
-  assertMatch: "node roof:height=5,5";
-  assertMatch: "node roof:height=12,00";
-  assertMatch: "node roof:height=12,5 ft";
-  assertNoMatch: "node roof:height=12,000";
-  assertNoMatch: "node roof:height=3,50,5";
-  assertNoMatch: "node roof:height=3.5";
-  assertNoMatch: "node roof:height=4";
-}
-
-*[maxlength][maxlength =~ /^[1-9][0-9]*(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxlength_meter_autofix;
-  fixAdd: concat("maxlength=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxlength")),1)," m");
   assertMatch: "node maxlength=6.78 meters";
   assertMatch: "node maxlength=5  metre";
@@ -186,32 +117,4 @@
   assertNoMatch: "node maxlength=2 m";
   assertNoMatch: "node maxlength=5";
-}
-*[maxlength][maxlength =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxlength_foot_autofix;
-  fixAdd: concat("maxlength=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxlength")),1)," ft");
-  assertMatch: "node maxlength=6.78 foot";
-  assertMatch: "node maxlength=5  Feet";
-  assertMatch: "node maxlength=2ft";
-  assertNoMatch: "node maxlength=2 ft";
-  assertNoMatch: "node maxlength=5";
-}
-*[maxlength][maxlength =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("maxlength=", replace(tag("maxlength"), ",", "."));
-  set maxlength_separator_autofix;
-  assertMatch: "node maxlength=5,5";
-  assertMatch: "node maxlength=12,00";
-  assertMatch: "node maxlength=12,5 ft";
-  assertNoMatch: "node maxlength=12,000";
-  assertNoMatch: "node maxlength=3,50,5";
-  assertNoMatch: "node maxlength=3.5";
-  assertNoMatch: "node maxlength=4";
-}
-
-*[width][width =~ /^[0-9]+(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set width_meter_autofix;
-  fixAdd: concat("width=", get(regexp_match("([0-9.]+)( )*(.+)",tag("width")),1)," m");
   assertMatch: "node width=6.78 meters";
   assertMatch: "node width=5  metre";
@@ -219,19 +122,100 @@
   assertNoMatch: "node width=2 m";
   assertNoMatch: "node width=5";
-}
-*[width][width =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set width_foot_autofix;
-  fixAdd: concat("width=", get(regexp_match("([0-9.]+)( )*(.+)",tag("width")),1)," ft");
+  assertMatch: "node maxwidth=6.78 meters";
+  assertMatch: "node maxwidth=5  metre";
+  assertMatch: "node maxwidth=2m";
+  assertNoMatch: "node maxwidth=2 m";
+  assertNoMatch: "node maxwidth=5";
+  assertMatch: "node min_height=6.78 meters";
+  assertMatch: "node min_height=5  metre";
+  assertMatch: "node min_height=2m";
+  assertNoMatch: "node min_height=2 m";
+  assertNoMatch: "node min_height=5";
+}
+
+/* Foot inches */
+*[height][height            =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/],
+*[maxheight][maxheight      =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/],
+*[roof:height][roof:height  =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/]!.zero_roof_height_flat,
+*[maxlength][maxlength      =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/],
+*[width][width              =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/],
+*[maxwidth][maxwidth        =~ /^(?i)[0-9]+([.,][0-9]+)?( *(foot|feet|ft)| +\')$/] {
+  set _unit_auto_fix;
+  throwWarning: tr("unusual value of {0}: use '' for foot and \" for inches, no spaces", "{0.key}");
+  fixAdd: concat("{0.key}", "=", get(regexp_match("([0-9.,]+) *.+", "{0.value}"), 1), "'");
+  assertMatch: "node height=6.78 foot";
+  assertMatch: "node height=5  Feet";
+  assertMatch: "node height=2 '";
+  assertNoMatch: "node height=2'";
+  assertNoMatch: "node height=5";
+  assertMatch: "node maxheight=6.78 foot";
+  assertMatch: "node maxheight=5  Feet";
+  assertMatch: "node maxheight=2 '";
+  assertNoMatch: "node maxheight=2'";
+  assertNoMatch: "node maxheight=5";
+  assertMatch: "node roof:height=6.78 foot";
+  assertMatch: "node roof:height=5  Feet";
+  assertMatch: "node roof:height=2 '";
+  assertNoMatch: "node roof:height=2'";
+  assertNoMatch: "node roof:height=5";
+  assertMatch: "node maxlength=6.78 foot";
+  assertMatch: "node maxlength=5  Feet";
+  assertMatch: "node maxlength=2 '";
+  assertNoMatch: "node maxlength=2'";
+  assertNoMatch: "node maxlength=5";
   assertMatch: "node width=6.78 foot";
   assertMatch: "node width=5  Feet";
-  assertMatch: "node width=2ft";
-  assertNoMatch: "node width=2 ft";
+  assertMatch: "node width=2 '";
+  assertNoMatch: "node width=2'";
   assertNoMatch: "node width=5";
-}
-*[width][width =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
+  assertMatch: "node maxwidth=6.78 foot";
+  assertMatch: "node maxwidth=5  Feet";
+  assertMatch: "node maxwidth=2 '";
+  assertNoMatch: "node maxwidth=2'";
+  assertNoMatch: "node maxwidth=5";
+}
+
+/* 2. Convert `,` to `.` */
+*[height][height            =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[maxheight][maxheight      =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[roof:height][roof:height  =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[maxlength][maxlength      =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[width][width              =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[maxwidth][maxwidth        =~   /^[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[min_height][min_height    =~ /^-?[0-9]+,[0-9][0-9]?( m|\')?$/],
+*[maxaxleload][maxaxleload  =~   /^[0-9]+,[0-9][0-9]?( (t|kg|st|lbs))?$/],
+*[maxweight][maxweight      =~   /^[0-9]+,[0-9][0-9]?( (t|kg|st|lbs))?$/],
+*[distance][distance        =~   /^[0-9]+,[0-9][0-9]?( (m|km|mi|nmi))?$/] {
+  set _unit_auto_fix;
   throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("width=", replace(tag("width"), ",", "."));
-  set width_separator_autofix;
+  fixAdd: concat("{0.key}", "=", replace(tag("{0.key}"), ",", "."));
+  assertMatch: "node height=5,5";
+  assertMatch: "node height=12,00";
+  assertMatch: "node height=12,5'";
+  assertNoMatch: "node height=12,000";
+  assertNoMatch: "node height=3,50,5";
+  assertNoMatch: "node height=3.5";
+  assertNoMatch: "node height=4";
+  assertMatch: "node maxheight=5,5";
+  assertMatch: "node maxheight=12,00";
+  assertMatch: "node maxheight=12,5'";
+  assertNoMatch: "node maxheight=12,000";
+  assertNoMatch: "node maxheight=3,50,5";
+  assertNoMatch: "node maxheight=3.5";
+  assertNoMatch: "node maxheight=4";
+  assertMatch: "node roof:height=5,5";
+  assertMatch: "node roof:height=12,00";
+  assertMatch: "node roof:height=12,5'";
+  assertNoMatch: "node roof:height=12,000";
+  assertNoMatch: "node roof:height=3,50,5";
+  assertNoMatch: "node roof:height=3.5";
+  assertNoMatch: "node roof:height=4";
+  assertMatch: "node maxlength=5,5";
+  assertMatch: "node maxlength=12,00";
+  assertMatch: "node maxlength=12,5'";
+  assertNoMatch: "node maxlength=12,000";
+  assertNoMatch: "node maxlength=3,50,5";
+  assertNoMatch: "node maxlength=3.5";
+  assertNoMatch: "node maxlength=4";
   assertMatch: "node width=5,5";
   assertMatch: "node width=12,00";
@@ -240,30 +224,4 @@
   assertNoMatch: "node width=3.5";
   assertNoMatch: "node width=4";
-}
-
-*[maxwidth][maxwidth=~ /^[0-9]+(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxwidth_meter_autofix;
-  fixAdd: concat("maxwidth=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxwidth")),1)," m");
-  assertMatch: "node maxwidth=6.78 meters";
-  assertMatch: "node maxwidth=5  metre";
-  assertMatch: "node maxwidth=2m";
-  assertNoMatch: "node maxwidth=2 m";
-  assertNoMatch: "node maxwidth=5";
-}
-*[maxwidth][maxwidth =~ /^[0-9]+(\.[0-9]+)?(( )*(foot|Foot|feet|Feet)|ft)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  set maxwidth_foot_autofix;
-  fixAdd: concat("maxwidth=", get(regexp_match("([0-9.]+)( )*(.+)",tag("maxwidth")),1)," ft");
-  assertMatch: "node maxwidth=6.78 foot";
-  assertMatch: "node maxwidth=5  Feet";
-  assertMatch: "node maxwidth=2ft";
-  assertNoMatch: "node maxwidth=2 ft";
-  assertNoMatch: "node maxwidth=5";
-}
-*[maxwidth][maxwidth =~ /^[0-9]+,[0-9][0-9]?( (m|ft))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("maxwidth=", replace(tag("maxwidth"), ",", "."));
-  set maxwidth_separator_autofix;
   assertMatch: "node maxwidth=5,5";
   assertMatch: "node maxwidth=12,00";
@@ -272,13 +230,41 @@
   assertNoMatch: "node maxwidth=3.5";
   assertNoMatch: "node maxwidth=4";
-}
-
-*[height     ][height      !~ /^(([0-9]+(\.[0-9]+)?( (m|ft))?)|([1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!.height_separator_autofix!.height_meter_autofix!.height_foot_autofix,
-*[maxheight  ][maxheight   !~ /^(([1-9][0-9]*(\.[0-9]+)?( (m|ft))?)|([0-9]+\'(([0-9]|10|11)(\.[0-9]*)?\")?)|none|default|below_default)$/]!.maxheight_separator_autofix!.maxheight_meter_autofix!.maxheight_foot_autofix,
-*[roof:height][roof:height !~ /^(([0-9]+(\.[0-9]+)?( (m|ft))?)|([1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!.roof_height_separator_autofix!.roof_height_meter_autofix!.roof_height_foot_autofix!.zero_roof_height_flat,
-*[maxlength  ][maxlength   !~ /^(([1-9][0-9]*(\.[0-9]+)?( (m|ft))?)|([0-9]+\'(([0-9]|10|11)(\.[0-9]*)?\")?)|none|default|below_default)$/]!.maxlength_separator_autofix!.maxlength_meter_autofix!.maxlength_foot_autofix,
-*[width      ][width       !~ /^(([0-9]+(\.[0-9]+)?( (m|ft))?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/]!.width_separator_autofix!.width_meter_autofix!.width_foot_autofix,
-*[maxwidth   ][maxwidth    !~ /^(([0-9]+(\.[0-9]+)?( (m|ft))?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/]!.maxwidth_separator_autofix!.maxwidth_meter_autofix!.maxwidth_foot_autofix {
-  throwWarning: tr("unusual value of {0}: {1} is default; only positive values; point is decimal separator; if units, put space then unit", "{0.key}", tr("meters"));
+  assertMatch: "node min_height=5,5";
+  assertMatch: "node min_height=12,00";
+  assertMatch: "node min_height=12,5'";
+  assertNoMatch: "node min_height=12,000";
+  assertNoMatch: "node min_height=3,50,5";
+  assertNoMatch: "node min_height=3.5";
+  assertNoMatch: "node min_height=4";
+  assertMatch: "node maxaxleload=5,5";
+  assertMatch: "node maxaxleload=12,00";
+  assertNoMatch: "node maxaxleload=12,000";
+  assertNoMatch: "node maxaxleload=3,50,5";
+  assertNoMatch: "node maxaxleload=3.5";
+  assertNoMatch: "node maxaxleload=4";
+  assertMatch: "node maxweight=5,5";
+  assertMatch: "node maxweight=12,00";
+  assertNoMatch: "node maxweight=12,000";
+  assertNoMatch: "node maxweight=3,50,5";
+  assertNoMatch: "node maxweight=3.5";
+  assertNoMatch: "node maxweight=4";
+  assertMatch: "node distance=5,5";
+  assertMatch: "node distance=12,00";
+  assertNoMatch: "node distance=12,000";
+  assertNoMatch: "node distance=3,50,5";
+  assertNoMatch: "node distance=3.5";
+  assertNoMatch: "node distance=4";
+}
+/* 3. Convert to the default unit for usage later. Use the tag prefixed with _. */
+/* 4. Start doing comparison checks. */
+
+*[height     ][height      !~ /^(([0-9]+(\.[0-9]+)?( m)?)|([1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!._unit_auto_fix,
+*[maxheight  ][maxheight   !~ /^(([1-9][0-9]*(\.[0-9]+)?( m)?)|([0-9]+\'(([0-9]|10|11)(\.[0-9]*)?\")?)|none|default|below_default)$/]!._unit_auto_fix,
+*[min_height ][min_height  !~ /^(-?([0-9]+(\.[0-9]+)?( m)?)|(-?[1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!._unit_auto_fix,
+*[roof:height][roof:height !~ /^(([0-9]+(\.[0-9]+)?( m)?)|([1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!.zero_roof_height_flat!._unit_auto_fix,
+*[maxlength  ][maxlength   !~ /^(([1-9][0-9]*(\.[0-9]+)?( m)?)|([0-9]+\'(([0-9]|10|11)(\.[0-9]*)?\")?)|none|default|below_default)$/]!._unit_auto_fix,
+*[width      ][width       !~ /^(([0-9]+(\.[0-9]+)?( m)?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/]!._unit_auto_fix,
+*[maxwidth   ][maxwidth    !~ /^(([0-9]+(\.[0-9]+)?( m)?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/]!._unit_auto_fix {
+  throwWarning: tr("unusual value of {0}: meters is default; only positive values; point is decimal separator; if units, put space then unit", "{0.key}");
   assertMatch: "node height=medium";
   assertMatch: "node maxheight=-5";
@@ -288,11 +274,13 @@
   assertMatch: "node maxheight=\"2. m\"";
   assertMatch: "node height=\"12. m\"";
-  assertNoMatch: "node height=6.78 meters";
-  assertNoMatch: "node height=5  metre";
-  assertNoMatch: "node height=2m";
+  assertNoMatch: "node height=6.78 m";
+  assertNoMatch: "node height=5  m";
+  assertNoMatch: "node height=2 m";
   assertNoMatch: "node height=3";
   assertNoMatch: "node height=2.22 m";
   assertNoMatch: "node height=7.8";
-  assertNoMatch: "node maxwidth=7 ft";
+  assertMatch: "node min_height=\"12. m\"";
+  assertNoMatch: "node min_height=-5";
+  assertNoMatch: "node maxwidth=7'";
   assertNoMatch: "node height=22'";
   assertNoMatch: "node width=10'5\"";
@@ -300,58 +288,6 @@
 }
 
-*[min_height][min_height =~ /^-?[0-9]+(\.[0-9]+)?(( )*(metre|metres|meter|meters|Metre|Metres|Meter|Meters)|m)$/] {
-  throwWarning: tr("unusual value of {0}: use abbreviation for unit and space between value and unit", "{0.key}");
-  fixAdd: concat("min_height=", get(regexp_match("(-?[0-9.]+)( )*(.+)",tag("min_height")),1)," m");
-  set min_height_meter_autofix;
-  assertMatch: "node min_height=6.78 meters";
-  assertMatch: "node min_height=5  metre";
-  assertMatch: "node min_height=2m";
-  assertNoMatch: "node min_height=2 m";
-  assertNoMatch: "node min_height=5";
-}
-*[min_height][min_height =~ /^-?[0-9]+,[0-9][0-9]?( m|\')?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("min_height=", replace(tag("min_height"), ",", "."));
-  set min_height_separator_autofix;
-  assertMatch: "node min_height=5,5";
-  assertMatch: "node min_height=12,00";
-  assertMatch: "node min_height=12,5'";
-  assertNoMatch: "node min_height=12,000";
-  assertNoMatch: "node min_height=3,50,5";
-  assertNoMatch: "node min_height=3.5";
-  assertNoMatch: "node min_height=4";
-}
-*[min_height ][min_height  !~ /^(-?([0-9]+(\.[0-9]+)?( m)?)|(-?[1-9][0-9]*\'((10|11|[0-9])((\.[0-9]+)?)\")?))$/]!.min_height_separator_autofix!.min_height_meter_autofix!.min_height_foot_autofix {
-  throwWarning: tr("unusual value of {0}: {1} is default; point is decimal separator; if units, put space then unit", "{0.key}", tr("meters"));
-  assertMatch: "node min_height=\"12. m\"";
-  assertNoMatch: "node min_height=-5";
-}
-
-*[maxaxleload][maxaxleload =~ /^[0-9]+,[0-9][0-9]?( (t|kg|st|lbs))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("maxaxleload=", replace(tag("maxaxleload"), ",", "."));
-  set maxaxleload_separator_autofix;
-  assertMatch: "node maxaxleload=5,5";
-  assertMatch: "node maxaxleload=12,00";
-  assertNoMatch: "node maxaxleload=12,000";
-  assertNoMatch: "node maxaxleload=3,50,5";
-  assertNoMatch: "node maxaxleload=3.5";
-  assertNoMatch: "node maxaxleload=4";
-}
-
-*[maxweight][maxweight =~ /^[0-9]+,[0-9][0-9]?( (t|kg|st|lbs))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("maxweight=", replace(tag("maxweight"), ",", "."));
-  set maxweight_separator_autofix;
-  assertMatch: "node maxweight=5,5";
-  assertMatch: "node maxweight=12,00";
-  assertNoMatch: "node maxweight=12,000";
-  assertNoMatch: "node maxweight=3,50,5";
-  assertNoMatch: "node maxweight=3.5";
-  assertNoMatch: "node maxweight=4";
-}
-
-*[maxaxleload][maxaxleload !~ /^([0-9]+(\.[0-9]+)?( (t|kg|st|lbs))?)$/]!.maxaxleload_separator_autofix,
-*[maxweight][maxweight !~ /^([0-9]+(\.[0-9]+)?( (t|kg|st|lbs))?)$/]!.maxweight_separator_autofix {
+*[maxaxleload][maxaxleload !~ /^([0-9]+(\.[0-9]+)?( (t|kg|st|lbs))?)$/],
+*[maxweight][maxweight !~ /^([0-9]+(\.[0-9]+)?( (t|kg|st|lbs))?)$/] {
   throwWarning: tr("unusual value of {0}: {1} is default; only positive values; point is decimal separator; if units, put space then unit", "{0.key}", tr("tonne"));
   assertMatch: "node maxaxleload=something";
@@ -380,16 +316,5 @@
 }
 
-*[distance][distance =~ /^[0-9]+,[0-9][0-9]?( (m|km|mi|nmi))?$/] {
-  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
-  fixAdd: concat("distance=", replace(tag("distance"), ",", "."));
-  set distance_separator_autofix;
-  assertMatch: "node distance=5,5";
-  assertMatch: "node distance=12,00";
-  assertNoMatch: "node distance=12,000";
-  assertNoMatch: "node distance=3,50,5";
-  assertNoMatch: "node distance=3.5";
-  assertNoMatch: "node distance=4";
-}
-*[distance][distance !~ /^(([0-9]+(\.[0-9]+)?( (m|km|mi|nmi))?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/]!.distance_separator_autofix {
+*[distance][distance !~ /^(([0-9]+(\.[0-9]+)?( (m|km|mi|nmi))?)|([0-9]+\'([0-9]+(\.[0-9]+)?\")?))$/] {
   throwWarning: tr("unusual value of {0}: {1} is default; only positive values; point is decimal separator; if units, put space then unit", "{0.key}", tr("kilometers"));
   assertMatch: "way distance=something";
@@ -421,4 +346,8 @@
   assertNoMatch: "way frequency=123.5 MHz";
 }
+
+/*******************
+ * End Unit checks *
+ *******************/
 
 way[gauge][gauge      =~ /^(broad|standard|narrow)$/],
@@ -524,5 +453,5 @@
   assertNoMatch: "node direction=45-100;190-250;300";
   assertNoMatch: "node direction=90;270";
-  assertNoMatch: "node direction=up"; 
+  assertNoMatch: "node direction=up";
   assertNoMatch: "node direction=down"; /* up/down are replaced by incline tag, has separate warning */
   assertMatch: "node direction=-10";
Index: trunk/resources/data/validator/wikipedia.mapcss
===================================================================
--- trunk/resources/data/validator/wikipedia.mapcss	(revision 18756)
+++ trunk/resources/data/validator/wikipedia.mapcss	(revision 18757)
@@ -55,6 +55,5 @@
 *[/^wikipedia:[-a-z]{2,12}$/][/^wikipedia:[-a-z]{2,12}$/ =~ /(?i).*%[0-9A-F][0-9A-F]/] {
   throwError: tr("{0} tag should not have URL-encoded values like ''%27''", "{0.key}");
-  /* fixAdd: concat("{0.key}", "=", get(regexp_match("(?i)^([-a-z]+:)(.*)$", tag("{0.key}")),1), trim(replace(URL_decode(get(regexp_match("(?i)^([-a-z]+:)(.+)$", tag("{0.key}")),2)), "_", " "))); */
-    /* tag("{0.key}") is not yet supported */
+  fixAdd: concat("{0.key}", "=", get(regexp_match("(?i)^([-a-z]+:)?(.*)$", tag("{0.key}")),1), trim(replace(URL_decode(get(println(regexp_match("(?i)^([-a-z]+:)?(.+)$", tag("{0.key}"))),2)), "_", " ")));
   assertMatch: "node wikipedia:de=Foo%27s";
   assertNoMatch: "node wikipedia:de=Foo";
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java	(revision 18757)
@@ -232,7 +232,8 @@
                 final Selector selector = check.whichSelectorMatchesEnvironment(env);
                 if (selector != null) {
-                    check.rule.declaration.execute(env);
+                    final Environment envWithSelector = env.withSelector(selector);
+                    check.rule.declaration.execute(envWithSelector);
                     if (!ignoreError && !check.errors.isEmpty()) {
-                        r.addAll(check.getErrorsForPrimitive(p, selector, env, new MapCSSTagCheckerAndRule(check.rule)));
+                        r.addAll(check.getErrorsForPrimitive(p, selector, envWithSelector, new MapCSSTagCheckerAndRule(check.rule)));
                     }
                 }
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerAsserts.java	(revision 18757)
@@ -75,6 +75,6 @@
                 Command fix = check.fixPrimitive(p);
                 if (fix != null && fix.executeCommand() && !MapCSSTagChecker.getErrorsForPrimitive(p, true, checksToRun).isEmpty()) {
-                    assertionConsumer.accept(MessageFormat.format("Autofix does not work for test ''{0}'' (i.e., {1})",
-                            check.getMessage(p), check.rule.selectors));
+                    assertionConsumer.accept(MessageFormat.format("Autofix does not work for test ''{0}'' (i.e., {1}). Failing test: {2}",
+                            check.getMessage(p), check.rule.selectors, i.getKey()));
                 }
             }
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerFixCommand.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerFixCommand.java	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerFixCommand.java	(revision 18757)
@@ -49,5 +49,5 @@
         final String s;
         if (obj instanceof Expression) {
-            s = (String) ((Expression) obj).evaluate(new Environment(p));
+            s = (String) ((Expression) obj).evaluate(new Environment(p).withSelector(matchingSelector));
         } else if (obj instanceof String) {
             s = (String) obj;
Index: trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java	(revision 18757)
@@ -18,6 +18,4 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -27,5 +25,4 @@
 import org.openstreetmap.josm.data.osm.IPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
@@ -35,10 +32,11 @@
 import org.openstreetmap.josm.gui.mappaint.Environment;
 import org.openstreetmap.josm.gui.mappaint.Keyword;
+import org.openstreetmap.josm.gui.mappaint.MultiCascade;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
-import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.TagCondition;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
+import org.openstreetmap.josm.gui.mappaint.mapcss.PlaceholderExpression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
@@ -215,5 +213,5 @@
 
     Selector whichSelectorMatchesPrimitive(OsmPrimitive primitive) {
-        return whichSelectorMatchesEnvironment(new Environment(primitive));
+        return whichSelectorMatchesEnvironment(new Environment(primitive, new MultiCascade(), Environment.DEFAULT_LAYER, null));
     }
 
@@ -226,35 +224,4 @@
 
     /**
-     * Determines the {@code index}-th key/value/tag (depending on {@code type}) of the
-     * {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector}.
-     *
-     * @param matchingSelector matching selector
-     * @param index            index
-     * @param type             selector type ("key", "value" or "tag")
-     * @param p                OSM primitive
-     * @return argument value, can be {@code null}
-     */
-    static String determineArgument(Selector.GeneralSelector matchingSelector, int index, String type, OsmPrimitive p) {
-        try {
-            final Condition c = matchingSelector.getConditions().get(index);
-            final Tag tag = c instanceof TagCondition
-                    ? ((TagCondition) c).asTag(p)
-                    : null;
-            if (tag == null) {
-                return null;
-            } else if ("key".equals(type)) {
-                return tag.getKey();
-            } else if ("value".equals(type)) {
-                return tag.getValue();
-            } else if ("tag".equals(type)) {
-                return tag.toString();
-            }
-        } catch (IndexOutOfBoundsException ignore) {
-            Logging.debug(ignore);
-        }
-        return null;
-    }
-
-    /**
      * Replaces occurrences of <code>{i.key}</code>, <code>{i.value}</code>, <code>{i.tag}</code> in {@code s} by the corresponding
      * key/value/tag of the {@code index}-th {@link Condition} of {@code matchingSelector}.
@@ -266,23 +233,5 @@
      */
     static String insertArguments(Selector matchingSelector, String s, OsmPrimitive p) {
-        if (s != null && matchingSelector instanceof Selector.ChildOrParentSelector) {
-            return insertArguments(((Selector.ChildOrParentSelector) matchingSelector).right, s, p);
-        } else if (s == null || !(matchingSelector instanceof Selector.GeneralSelector)) {
-            return s;
-        }
-        final Matcher m = Pattern.compile("\\{(\\d+)\\.(key|value|tag)\\}").matcher(s);
-        final StringBuffer sb = new StringBuffer();
-        while (m.find()) {
-            final String argument = determineArgument((Selector.GeneralSelector) matchingSelector,
-                    Integer.parseInt(m.group(1)), m.group(2), p);
-            try {
-                // Perform replacement with null-safe + regex-safe handling
-                m.appendReplacement(sb, String.valueOf(argument).replace("^(", "").replace(")$", ""));
-            } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
-                Logging.log(Logging.LEVEL_ERROR, tr("Unable to replace argument {0} in {1}: {2}", argument, sb, e.getMessage()), e);
-            }
-        }
-        m.appendTail(sb);
-        return sb.toString();
+        return PlaceholderExpression.insertArguments(matchingSelector, s, p);
     }
 
@@ -329,5 +278,5 @@
             return String.valueOf(
                     val instanceof Expression
-                            ? ((Expression) val).evaluate(new Environment(p))
+                            ? ((Expression) val).evaluate(new Environment(p).withSelector(p == null ? null : whichSelectorMatchesPrimitive(p)))
                             : val
             );
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/Environment.java	(revision 18757)
@@ -13,4 +13,5 @@
 import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
+import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
@@ -42,4 +43,7 @@
     private Context context = Context.PRIMITIVE;
 
+    /** The selector that is currently being evaluated */
+    private final Selector selector;
+
     /**
      * The name of the default layer. It is used if no layer is specified in the MapCSS rule
@@ -98,4 +102,5 @@
     public Environment() {
         // environment can be initialized later through with* methods
+        this.selector = null;
     }
 
@@ -107,5 +112,5 @@
      */
     public Environment(IPrimitive osm) {
-        this.osm = osm;
+        this(osm, null, null, null);
     }
 
@@ -123,4 +128,5 @@
         this.layer = layer;
         this.source = source;
+        this.selector = null;
     }
 
@@ -132,4 +138,15 @@
      */
     public Environment(Environment other) {
+        this(other, other.selector);
+    }
+
+    /**
+     * Creates a clone of the environment {@code other}.
+     *
+     * @param other the other environment. Must not be null.
+     * @param selector the selector for this environment. May be null.
+     * @throws IllegalArgumentException if {@code param} is {@code null}
+     */
+    private Environment(Environment other, Selector selector) {
         CheckParameterUtil.ensureParameterNotNull(other);
         this.osm = other.osm;
@@ -147,4 +164,5 @@
         this.mpAreaCache = other.mpAreaCache;
         this.toMatchForSurrounding = other.toMatchForSurrounding;
+        this.selector = selector;
     }
 
@@ -264,4 +282,14 @@
 
     /**
+     * Creates a clone of this environment, with the selector set
+     * @param selector The selector to use
+     * @return A clone of this environment, with the specified selector
+     * @since xxx
+     */
+    public Environment withSelector(Selector selector) {
+        return new Environment(this, selector);
+    }
+
+    /**
      * Determines if the context of this environment is {@link Context#LINK}.
      * @return {@code true} if the context of this environment is {@code Context#LINK}, {@code false} otherwise
@@ -302,4 +330,13 @@
             return ((Relation) osm).getMember(index).getRole();
         return null;
+    }
+
+    /**
+     * Get the selector for this environment
+     * @return The selector. May be {@code null}.
+     * @since xxx
+     */
+    public Selector selector() {
+        return this.selector;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 18756)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 18757)
@@ -32,4 +32,5 @@
 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
+import org.openstreetmap.josm.gui.mappaint.mapcss.PlaceholderExpression;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
 import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.ChildOrParentSelector;
@@ -1053,4 +1054,7 @@
             if (lit == null)
                 return NullExpression.INSTANCE;
+            else if (lit instanceof String && PlaceholderExpression.PATTERN_PLACEHOLDER.matcher((String) lit).find()) {
+                return new PlaceholderExpression((String) lit);
+            }
             return new LiteralExpression(lit);
         }
Index: trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java	(revision 18756)
+++ trunk/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java	(revision 18757)
@@ -44,4 +44,6 @@
 import org.openstreetmap.josm.io.OsmReader;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
+import org.openstreetmap.josm.testutils.annotations.Projection;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -51,4 +53,6 @@
  * JUnit Test of {@link MapCSSTagChecker}.
  */
+@BasicPreferences
+@Projection
 class MapCSSTagCheckerTest {
 
@@ -58,5 +62,5 @@
     @RegisterExtension
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules().projection().territories().preferences();
+    public JOSMTestRules test = new JOSMTestRules().territories();
 
     /**
@@ -96,5 +100,5 @@
         assertNotNull(check);
         assertEquals("{0.key}=null is deprecated", check.getDescription(null));
-        assertEquals("fixRemove: {0.key}", check.fixCommands.get(0).toString());
+        assertEquals("fixRemove: <{0.key}>", check.fixCommands.get(0).toString());
         assertEquals("fixAdd: natural=wetland", check.fixCommands.get(1).toString());
         assertEquals("fixAdd: wetland=marsh", check.fixCommands.get(2).toString());
