]> git.basschouten.com Git - openhab-addons.git/commitdiff
[map] Add a way to customize inline-map delimiters (#17327)
authorjimtng <2554958+jimtng@users.noreply.github.com>
Mon, 9 Sep 2024 21:01:56 +0000 (07:01 +1000)
committerGitHub <noreply@github.com>
Mon, 9 Sep 2024 21:01:56 +0000 (23:01 +0200)
* [map] Add a way to customize inline-map delimiters

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
bundles/org.openhab.transform.map/README.md
bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java
bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml
bundles/org.openhab.transform.map/src/main/resources/OH-INF/i18n/map.properties
bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java

index efc8317f1e5c79171dd47a52a0affae247bd29d3..715e7bfbebac263e6eb8aa910e81b69fa113a892 100644 (file)
@@ -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 <itemName> { channel="<channelUID>"[profile="transform:MAP", function="<filename>", sourceFormat="<valueFormat>"]}
+String <itemName> { channel="<channelUID>" [profile="transform:MAP", function="<filename>", sourceFormat="<valueFormat>" ] }
 ```
 
 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 <itemName> { channel="<channelUID>" [ profile="transform:MAP", function="|open=ON;closed=OFF" ] }
+```
index c17d5be0b4763b750f9d0cdf074b943d4c86e7bb..d08307d79cbc445edf354d3c6af1a56665e6c48d 100644 (file)
@@ -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<String> SUPPORTED_CONFIGURATION_TYPES = Set.of("map");
-    private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern.compile("\\s*\\|(?<map>.+)", Pattern.DOTALL);
+    private static final String INLINE_MAP_DEFAULT_DELIMITER = ";";
+    private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern
+            .compile("\\s*\\|(?:\\?delimiter=(?<delimiter>\\W+?))?(?<map>.+)", 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);
index b07c41a586baf020a553a7e8609d182b2cfce24f..6cb4d1ece343d5e7d99142ba248ed9aa917dbbca 100644 (file)
@@ -7,7 +7,16 @@
        <config-description uri="profile:transform:MAP">
                <parameter name="function" type="text" required="true">
                        <label>Filename</label>
-                       <description>Filename containing the mapping information.</description>
+                       <description><![CDATA[Filename containing the mapping information.
+                       <br /><br />
+                       Inline map is supported, e.g. "|online=ON;offline=OFF".
+                       <br /><br />
+                       The inline map entries are delimited with semicolons ("<code>;</code>") by default.
+                       <br />
+                       To use a different delimiter, for example a comma: "<code>|?delimiter=,;online=ON,offline=OFF</code>"
+                       <br />
+                       To use "<code>?delimiter</code>" as an actual map key, do not place it at the beginning of the map.
+                       ]]></description>
                        <limitToOptions>false</limitToOptions>
                </parameter>
                <parameter name="sourceFormat" type="text">
index 7c22c32c382197ba66ab4a27e5a6503f09f41073..40613cc5cdc7b8a5bf54f8cd71f6443e35aa7100 100644 (file)
@@ -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.<br /><br />Inline map is supported, e.g. "|online=ON;offline=OFF".<br /><br />The inline map entries are delimited with semicolons ("<code>;</code>") by default. <br /> To use a different delimiter, for example a comma: "<code>|?delimiter=,;online=ON,offline=OFF</code>" <br /> To use "<code>?delimiter</code>" 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).
index 28fd8ead8739189ab1f5dad94c737a061bbeb5c9..01994aaeafd33cec2f7891f21e17f47013ec9fb5 100644 (file)
@@ -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"));
+    }
 }