Index: trunk/src/org/openstreetmap/josm/data/osm/SimplePrimitiveId.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/SimplePrimitiveId.java	(revision 18828)
+++ trunk/src/org/openstreetmap/josm/data/osm/SimplePrimitiveId.java	(revision 18829)
@@ -99,5 +99,5 @@
         final Matcher m = MULTIPLE_IDS_PATTERN.matcher(s);
         if (m.matches()) {
-            return extractIdsInto(m, new ArrayList<SimplePrimitiveId>());
+            return extractIdsInto(m, new ArrayList<>());
         } else {
             throw new IllegalArgumentException("The string " + s + " does not match the pattern " + MULTIPLE_IDS_PATTERN);
@@ -149,3 +149,24 @@
         return firstChar == 'n' ? OsmPrimitiveType.NODE : firstChar == 'w' ? OsmPrimitiveType.WAY : OsmPrimitiveType.RELATION;
     }
+
+    /**
+     * Convert a primitive to a simple id
+     *
+     * @param primitive The primitive to convert
+     * @return The type (may be n, w, or r, or something else) + the id (e.g., w42)
+     * @since 18829
+     */
+    public static String toSimpleId(PrimitiveId primitive) {
+        switch (primitive.getType()) {
+            case NODE:
+                return "n" + primitive.getUniqueId();
+            case CLOSEDWAY:
+            case WAY:
+                return "w" + primitive.getUniqueId();
+            case MULTIPOLYGON:
+            case RELATION:
+                return "r" + primitive.getUniqueId();
+        }
+        throw new IllegalArgumentException("Unknown primitive type: " + primitive.getType());
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 18828)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java	(revision 18829)
@@ -7,4 +7,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Array;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -18,4 +20,5 @@
 import java.util.function.Function;
 
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.gui.mappaint.Cascade;
 import org.openstreetmap.josm.gui.mappaint.Environment;
@@ -101,5 +104,5 @@
                                     BiFunction<T, U, ?> biFunction, TriFunction<T, U, V, ?> triFunction) {
             return args -> env -> {
-                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
+                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
                 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
                 V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
@@ -111,5 +114,5 @@
                                        QuadFunction<T, U, V, W, ?> function) {
             return args -> env -> {
-                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
+                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
                 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
                 V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
@@ -119,8 +122,40 @@
         }
 
-        static <T> Factory ofEnv(Function<Environment, ?> function) {
+        /**
+         * Create a factory that accepts an iterable (array <i>or</i> generic iterable)
+         * @param type The expected type class
+         * @param function The function to apply the arguments to
+         * @param <T> The iterable type
+         * @return The result of the function call
+         */
+        @SuppressWarnings("unchecked")
+        static <T> Factory ofIterable(Class<T> type, Function<Iterable<T>, ?> function) {
+            return args -> env -> {
+                Object arg0 = args.get(0).evaluate(env);
+                if (args.size() == 1 && arg0 instanceof Iterable) {
+                    return function.apply((Iterable<T>) arg0);
+                } else {
+                    return function.apply(Arrays.asList(args.stream().map(arg -> Cascade.convertTo(arg, type))
+                            .toArray(length -> (T[]) Array.newInstance(type, length))));
+                }
+            };
+        }
+
+        /**
+         * Create a {@link Factory} for a function
+         * @param function The function to use
+         * @return The result of the function
+         */
+        static Factory ofEnv(Function<Environment, ?> function) {
             return args -> function::apply;
         }
 
+        /**
+         * Create a {@link Factory} for a function that takes a parameter
+         * @param type The parameter type class
+         * @param function The function to use when one argument is available
+         * @param <T> the type of the input to the function
+         * @return The result of the function
+         */
         static <T> Factory ofEnv(Class<T> type, BiFunction<Environment, T, ?> function) {
             return args -> env -> {
@@ -130,10 +165,40 @@
         }
 
+        /**
+         * Create a {@link Factory} for an overloaded function
+         * @param type1 The first parameter type class
+         * @param type2 The second parameter type class
+         * @param biFunction The function to use when one argument is available
+         * @param triFunction The function to use when two arguments are available
+         * @param <T> the type of the input to the function
+         * @param <U> the type of the input to the function
+         * @return The result of one of the functions
+         */
         static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2,
                                     BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
             return args -> env -> {
-                T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
+                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
                 U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
                 return v1 == null ? null : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
+            };
+        }
+
+        /**
+         * Create a {@link Factory} for an overloaded function
+         * @param type1 The first parameter type class
+         * @param type2 The second parameter type class
+         * @param function The function to use when no args are available
+         * @param biFunction The function to use when one argument is available
+         * @param triFunction The function to use when two arguments are available
+         * @param <T> the type of the input to the function
+         * @param <U> the type of the input to the function
+         * @return The result of one of the functions
+         */
+        static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2, Function<Environment, ?> function,
+                                    BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
+            return args -> env -> {
+                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
+                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
+                return v1 == null ? function.apply(env) : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
             };
         }
@@ -170,4 +235,6 @@
         FACTORY_MAP.put("color2html", Factory.of(Color.class, Functions::color2html));
         FACTORY_MAP.put("concat", Factory.ofObjectVarargs(Functions::concat));
+        FACTORY_MAP.put("convert_primitive_to_string", Factory.of(PrimitiveId.class, Functions::convert_primitive_to_string));
+        FACTORY_MAP.put("convert_primitives_to_string", Factory.ofIterable(PrimitiveId.class, Functions::convert_primitives_to_string));
         FACTORY_MAP.put("cos", Factory.of(Math::cos));
         FACTORY_MAP.put("cosh", Factory.of(Math::cosh));
@@ -215,4 +282,6 @@
         FACTORY_MAP.put("outside", Factory.ofEnv(String.class, Functions::outside));
         FACTORY_MAP.put("parent_osm_id", Factory.ofEnv(Functions::parent_osm_id));
+        FACTORY_MAP.put("parent_osm_primitives", Factory.ofEnv(String.class, String.class,
+                Functions::parent_osm_primitives, Functions::parent_osm_primitives, Functions::parent_osm_primitives));
         FACTORY_MAP.put("parent_tag", Factory.ofEnv(String.class, Functions::parent_tag));
         FACTORY_MAP.put("parent_tags", Factory.ofEnv(String.class, Functions::parent_tags));
@@ -391,7 +460,7 @@
     /**
      * Function to calculate the length of a string or list in a MapCSS eval expression.
-     *
+     * <p>
      * Separate implementation to support overloading for different argument types.
-     *
+     * <p>
      * The use for calculating the length of a list is deprecated, use
      * {@link Functions#count(java.util.List)} instead (see #10061).
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java	(revision 18828)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java	(revision 18829)
@@ -4,5 +4,7 @@
 import java.awt.Color;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -21,5 +23,7 @@
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.search.SearchCompiler;
@@ -46,5 +50,5 @@
 /**
  * List of functions that can be used in MapCSS expressions.
- *
+ * <p>
  * First parameter can be of type {@link Environment} (if needed). This is
  * automatically filled in by JOSM and the user only sees the remaining arguments.
@@ -458,5 +462,5 @@
     /**
      * Gets a list of all non-null values of the key {@code key} from the object's parent(s).
-     *
+     * <p>
      * The values are sorted according to {@link AlphanumComparator}.
      * @param env the environment
@@ -520,4 +524,84 @@
 
     /**
+     * Gets a list of all OSM id's of the object's parent(s) with a specified key.
+     *
+     * @param env      the environment
+     * @param key      the OSM key
+     * @param keyValue the regex value of the OSM key
+     * @return a list of non-null values of the OSM id's from the object's parent(s)
+     * @since 18829
+     */
+    @NullableArguments
+    public static List<IPrimitive> parent_osm_primitives(final Environment env, String key, String keyValue) {
+         if (env.parent == null) {
+             if (env.osm != null) {
+                final ArrayList<IPrimitive> parents = new ArrayList<>();
+                for (IPrimitive parent : env.osm.getReferrers()) {
+                    if ((key == null || parent.get(key) != null)
+                            && (keyValue == null || regexp_test(keyValue, parent.get(key)))) {
+                        parents.add(parent);
+                    }
+                }
+                return Collections.unmodifiableList(parents);
+            }
+            return Collections.emptyList();
+         }
+         return Collections.singletonList(env.parent);
+     }
+
+     /**
+      * Gets a list of all OSM id's of the object's parent(s) with a specified key.
+      *
+      * @param env the environment
+      * @param key the OSM key
+      * @return a list of non-null values of the OSM id's from the object's parent(s)
+      * @since 18829
+      */
+     @NullableArguments
+     public static List<IPrimitive> parent_osm_primitives(final Environment env, String key) {
+         return parent_osm_primitives(env, key, null);
+     }
+
+    /**
+     * Gets a list of all OSM id's of the object's parent(s).
+     *
+     * @param env the environment
+     * @return a list of non-null values of the OSM id's from the object's parent(s)
+     * @since 18829
+     */
+    public static List<IPrimitive> parent_osm_primitives(final Environment env) {
+        return parent_osm_primitives(env, null, null);
+    }
+
+    /**
+     * Convert Primitives to a string
+     *
+     * @param primitives The primitives to convert
+     * @return A list of strings in the format type + id (in the list order)
+     * @see SimplePrimitiveId#toSimpleId
+     * @since 18829
+     */
+    public static List<String> convert_primitives_to_string(Iterable<PrimitiveId> primitives) {
+        final List<String> primitiveStrings = new ArrayList<>(primitives instanceof Collection ?
+                ((Collection<?>) primitives).size() : 0);
+        for (PrimitiveId primitive : primitives) {
+            primitiveStrings.add(convert_primitive_to_string(primitive));
+        }
+        return primitiveStrings;
+    }
+
+    /**
+     * Convert a primitive to a string
+     *
+     * @param primitive The primitive to convert
+     * @return A string in the format type + id
+     * @see SimplePrimitiveId#toSimpleId
+     * @since 18829
+     */
+    public static String convert_primitive_to_string(PrimitiveId primitive) {
+        return SimplePrimitiveId.toSimpleId(primitive);
+    }
+
+    /**
      * Returns the lowest distance between the OSM object and a GPX point
      * <p>
@@ -554,5 +638,5 @@
             return null;
         }
-        return Float.valueOf(env.index + 1f);
+        return env.index + 1f;
     }
 
@@ -726,6 +810,6 @@
         try {
             return RotationAngle.parseCardinalRotation(cardinal);
-        } catch (IllegalArgumentException ignore) {
-            Logging.trace(ignore);
+        } catch (IllegalArgumentException illegalArgumentException) {
+            Logging.trace(illegalArgumentException);
             return null;
         }
@@ -778,5 +862,5 @@
      * Obtains the JOSM key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
      * and defaults to {@code def} if that is null.
-     *
+     * <p>
      * If the default value can be {@linkplain Cascade#convertTo converted} to a {@link Color},
      * the {@link NamedColorProperty} is retrieved as string.
@@ -989,5 +1073,5 @@
     /**
      * Returns a title-cased version of the string where words start with an uppercase character and the remaining characters are lowercase
-     *
+     * <p>
      * Also known as "capitalize".
      * @param str The source string
@@ -1053,5 +1137,7 @@
 
     /**
-     * Percent-decode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
+     * Percent-decode a string. (See
+     * <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
+     * <p>
      * This is especially useful for wikipedia titles
      * @param s url-encoded string
@@ -1070,5 +1156,7 @@
 
     /**
-     * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
+     * Percent-encode a string.
+     * (See <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
+     * <p>
      * This is especially useful for data urls, e.g.
      * <code>concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
@@ -1082,5 +1170,5 @@
     /**
      * XML-encode a string.
-     *
+     * <p>
      * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
      * @param s arbitrary string
