diff --git a/bundles/org.openhab.transform.map/README.md b/bundles/org.openhab.transform.map/README.md index efc8317f1e5..715e7bfbeba 100644 --- a/bundles/org.openhab.transform.map/README.md +++ b/bundles/org.openhab.transform.map/README.md @@ -21,11 +21,20 @@ To organize the various transformations one might use subfolders. ## Inline Map -Instead of providing the file name from which to load, the mapping table can be specified inline by prefixing it with the `|` character. -The "key=value" pairs are separated with a semicolon (`;`) or a newline character. +Instead of providing the file name from which to load, the mapping table can be specified inline by prefixing it with the pipe character `|` . +The inline map entries are delimited with semicolons (`;`) by default. For example, the following map function translates open/closed to ON/OFF: `|open=ON; closed=OFF` +The delimiters can be changed by adding `?delimiter=` immediately after the pipe character `|`. +Some examples of changing to different delimiters: + +- `|?delimiter=,online=ON,offline=OFF` +- `|?delimiter=|online=ON|offline=OFF` +- `|?delimiter=##online=ON##offline=OFF` + +To use `?delimiter` as an actual map key, do not place it at the beginning of the map. + ## Example transform/binary.map: @@ -54,7 +63,7 @@ The functionality of this `TransformationService` can be used in a `Profile` on To do so, it can be configured in the `.items` file as follows: ```java -String { channel=""[profile="transform:MAP", function="", sourceFormat=""]} +String { channel="" [profile="transform:MAP", function="", sourceFormat="" ] } ``` The mapping filename (within the `transform` folder) has to be set in the `function` parameter. @@ -62,3 +71,9 @@ The parameter `sourceFormat` is optional and can be used to format the input val If omitted the default is `%s`, so the input value will be put into the transformation without any format changes. Please note: This profile is a one-way transformation, i.e. only values from a device towards the item are changed, the other direction is left untouched. + +To use an inline map in the profile: + +```java +String { channel="" [ profile="transform:MAP", function="|open=ON;closed=OFF" ] } +``` diff --git a/bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java b/bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java index c17d5be0b47..d08307d79cb 100644 --- a/bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java +++ b/bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java @@ -20,6 +20,8 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -60,7 +62,9 @@ public class MapTransformationService private static final String PROFILE_CONFIG_URI = "profile:transform:MAP"; private static final String CONFIG_PARAM_FUNCTION = "function"; private static final Set SUPPORTED_CONFIGURATION_TYPES = Set.of("map"); - private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern.compile("\\s*\\|(?.+)", Pattern.DOTALL); + private static final String INLINE_MAP_DEFAULT_DELIMITER = ";"; + private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern + .compile("\\s*\\|(?:\\?delimiter=(?\\W+?))?(?.+)", Pattern.DOTALL); private final Logger logger = LoggerFactory.getLogger(MapTransformationService.class); private final TransformationRegistry transformationRegistry; @@ -87,9 +91,9 @@ public class MapTransformationService properties = cachedInlineMap.computeIfAbsent(function, f -> { Properties props = new Properties(); String map = matcher.group("map").trim(); - if (!map.contains("\n")) { - map = map.replace(";", "\n"); - } + String delimiter = Objects.requireNonNull(Optional.ofNullable(matcher.group("delimiter")) + .map(String::trim).orElse(INLINE_MAP_DEFAULT_DELIMITER)); + map = map.replace(delimiter, "\n"); try { props.load(new StringReader(map)); logger.trace("Parsed inline map configuration '{}'", props); diff --git a/bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml b/bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml index b07c41a586b..6cb4d1ece34 100644 --- a/bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml +++ b/bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml @@ -7,7 +7,16 @@ - Filename containing the mapping information. +
+ Inline map is supported, e.g. "|online=ON;offline=OFF". +

+ The inline map entries are delimited with semicolons (";") by default. +
+ To use a different delimiter, for example a comma: "|?delimiter=,;online=ON,offline=OFF" +
+ To use "?delimiter" as an actual map key, do not place it at the beginning of the map. + ]]>
false
diff --git a/bundles/org.openhab.transform.map/src/main/resources/OH-INF/i18n/map.properties b/bundles/org.openhab.transform.map/src/main/resources/OH-INF/i18n/map.properties index 7c22c32c382..40613cc5cdc 100644 --- a/bundles/org.openhab.transform.map/src/main/resources/OH-INF/i18n/map.properties +++ b/bundles/org.openhab.transform.map/src/main/resources/OH-INF/i18n/map.properties @@ -1,7 +1,12 @@ +# add-on + +addon.map.name = MAP transformation +addon.map.description = Transforms the input by mapping it to another string. + # bundle config profile-type.transform.MAP.label = MAP profile.config.transform.MAP.function.label = Filename -profile.config.transform.MAP.function.description = Filename containing the mapping information. +profile.config.transform.MAP.function.description = Filename containing the mapping information.

Inline map is supported, e.g. "|online=ON;offline=OFF".

The inline map entries are delimited with semicolons (";") by default.
To use a different delimiter, for example a comma: "|?delimiter=,;online=ON,offline=OFF"
To use "?delimiter" as an actual map key, do not place it at the beginning of the map. profile.config.transform.MAP.sourceFormat.label = State Formatter profile.config.transform.MAP.sourceFormat.description = How to format the state on the channel before transforming it, i.e. %s or %.1f °C (default is %s). diff --git a/bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java b/bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java index 28fd8ead873..01994aaeafd 100644 --- a/bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java +++ b/bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java @@ -159,12 +159,6 @@ public class MapTransformationServiceTest extends JavaTest { assertEquals("value2", processor.transform(transformation, "key2")); } - @Test - public void multiLineInlineMapTest() throws TransformationException { - String transformation = "|key1=semicolons_arent_separators;1 \n key2 = value;2"; - assertEquals("value;2", processor.transform(transformation, "key2")); - } - @Test public void defaultInlineTest() throws TransformationException { String transformation = "|key1=value1;key2=value;=default"; @@ -176,4 +170,22 @@ public class MapTransformationServiceTest extends JavaTest { String transformation = "|key1=value1;key2=value;=_source_"; assertEquals("nonexistent", processor.transform(transformation, "nonexistent")); } + + @Test + public void customSeparatorTest() throws TransformationException { + String transformation = "|?delimiter=,key1=value1;with;semicolons,key2;too=value2,?delimiter=value3"; + assertEquals("value1;with;semicolons", processor.transform(transformation, "key1")); + assertEquals("value2", processor.transform(transformation, "key2;too")); + assertEquals("value3", processor.transform(transformation, "?delimiter")); + + transformation = "|?delimiter=||key1=value1;with;semicolons||key2;too=value2||?delimiter=value3"; + assertEquals("value1;with;semicolons", processor.transform(transformation, "key1")); + assertEquals("value2", processor.transform(transformation, "key2;too")); + assertEquals("value3", processor.transform(transformation, "?delimiter")); + + transformation = "|key1=value1;key2=value2;?delimiter=value3"; + assertEquals("value1", processor.transform(transformation, "key1")); + assertEquals("value2", processor.transform(transformation, "key2")); + assertEquals("value3", processor.transform(transformation, "?delimiter")); + } }