Skip to content

Commit fa5fd1c

Browse files
authored
[map] Add a way to customize inline-map delimiters (openhab#17327)
* [map] Add a way to customize inline-map delimiters Signed-off-by: Jimmy Tanagra <[email protected]>
1 parent d2d2e63 commit fa5fd1c

File tree

5 files changed

+60
-15
lines changed

5 files changed

+60
-15
lines changed

bundles/org.openhab.transform.map/README.md

+18-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@ To organize the various transformations one might use subfolders.
2121

2222
## Inline Map
2323

24-
Instead of providing the file name from which to load, the mapping table can be specified inline by prefixing it with the `|` character.
25-
The "key=value" pairs are separated with a semicolon (`;`) or a newline character.
24+
Instead of providing the file name from which to load, the mapping table can be specified inline by prefixing it with the pipe character `|` .
2625

26+
The inline map entries are delimited with semicolons (`;`) by default.
2727
For example, the following map function translates open/closed to ON/OFF: `|open=ON; closed=OFF`
2828

29+
The delimiters can be changed by adding `?delimiter=` immediately after the pipe character `|`.
30+
Some examples of changing to different delimiters:
31+
32+
- `|?delimiter=,online=ON,offline=OFF`
33+
- `|?delimiter=|online=ON|offline=OFF`
34+
- `|?delimiter=##online=ON##offline=OFF`
35+
36+
To use `?delimiter` as an actual map key, do not place it at the beginning of the map.
37+
2938
## Example
3039

3140
transform/binary.map:
@@ -54,11 +63,17 @@ The functionality of this `TransformationService` can be used in a `Profile` on
5463
To do so, it can be configured in the `.items` file as follows:
5564

5665
```java
57-
String <itemName> { channel="<channelUID>"[profile="transform:MAP", function="<filename>", sourceFormat="<valueFormat>"]}
66+
String <itemName> { channel="<channelUID>" [profile="transform:MAP", function="<filename>", sourceFormat="<valueFormat>" ] }
5867
```
5968

6069
The mapping filename (within the `transform` folder) has to be set in the `function` parameter.
6170
The parameter `sourceFormat` is optional and can be used to format the input value **before** the transformation, i.e. `%.3f`.
6271
If omitted the default is `%s`, so the input value will be put into the transformation without any format changes.
6372

6473
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.
74+
75+
To use an inline map in the profile:
76+
77+
```java
78+
String <itemName> { channel="<channelUID>" [ profile="transform:MAP", function="|open=ON;closed=OFF" ] }
79+
```

bundles/org.openhab.transform.map/src/main/java/org/openhab/transform/map/internal/MapTransformationService.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Locale;
2121
import java.util.Map;
2222
import java.util.Map.Entry;
23+
import java.util.Objects;
24+
import java.util.Optional;
2325
import java.util.Properties;
2426
import java.util.Set;
2527
import java.util.concurrent.ConcurrentHashMap;
@@ -60,7 +62,9 @@ public class MapTransformationService
6062
private static final String PROFILE_CONFIG_URI = "profile:transform:MAP";
6163
private static final String CONFIG_PARAM_FUNCTION = "function";
6264
private static final Set<String> SUPPORTED_CONFIGURATION_TYPES = Set.of("map");
63-
private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern.compile("\\s*\\|(?<map>.+)", Pattern.DOTALL);
65+
private static final String INLINE_MAP_DEFAULT_DELIMITER = ";";
66+
private static final Pattern INLINE_MAP_CONFIG_PATTERN = Pattern
67+
.compile("\\s*\\|(?:\\?delimiter=(?<delimiter>\\W+?))?(?<map>.+)", Pattern.DOTALL);
6468

6569
private final Logger logger = LoggerFactory.getLogger(MapTransformationService.class);
6670
private final TransformationRegistry transformationRegistry;
@@ -87,9 +91,9 @@ public void deactivate() {
8791
properties = cachedInlineMap.computeIfAbsent(function, f -> {
8892
Properties props = new Properties();
8993
String map = matcher.group("map").trim();
90-
if (!map.contains("\n")) {
91-
map = map.replace(";", "\n");
92-
}
94+
String delimiter = Objects.requireNonNull(Optional.ofNullable(matcher.group("delimiter"))
95+
.map(String::trim).orElse(INLINE_MAP_DEFAULT_DELIMITER));
96+
map = map.replace(delimiter, "\n");
9397
try {
9498
props.load(new StringReader(map));
9599
logger.trace("Parsed inline map configuration '{}'", props);

bundles/org.openhab.transform.map/src/main/resources/OH-INF/config/mapProfile.xml

+10-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77
<config-description uri="profile:transform:MAP">
88
<parameter name="function" type="text" required="true">
99
<label>Filename</label>
10-
<description>Filename containing the mapping information.</description>
10+
<description><![CDATA[Filename containing the mapping information.
11+
<br /><br />
12+
Inline map is supported, e.g. "|online=ON;offline=OFF".
13+
<br /><br />
14+
The inline map entries are delimited with semicolons ("<code>;</code>") by default.
15+
<br />
16+
To use a different delimiter, for example a comma: "<code>|?delimiter=,;online=ON,offline=OFF</code>"
17+
<br />
18+
To use "<code>?delimiter</code>" as an actual map key, do not place it at the beginning of the map.
19+
]]></description>
1120
<limitToOptions>false</limitToOptions>
1221
</parameter>
1322
<parameter name="sourceFormat" type="text">
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
# add-on
2+
3+
addon.map.name = MAP transformation
4+
addon.map.description = Transforms the input by mapping it to another string.
5+
16
# bundle config
27

38
profile-type.transform.MAP.label = MAP
49
profile.config.transform.MAP.function.label = Filename
5-
profile.config.transform.MAP.function.description = Filename containing the mapping information.
10+
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.
611
profile.config.transform.MAP.sourceFormat.label = State Formatter
712
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).

bundles/org.openhab.transform.map/src/test/java/org/openhab/transform/map/internal/MapTransformationServiceTest.java

+18-6
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,6 @@ public void oneLineInlineMapTest() throws TransformationException {
159159
assertEquals("value2", processor.transform(transformation, "key2"));
160160
}
161161

162-
@Test
163-
public void multiLineInlineMapTest() throws TransformationException {
164-
String transformation = "|key1=semicolons_arent_separators;1 \n key2 = value;2";
165-
assertEquals("value;2", processor.transform(transformation, "key2"));
166-
}
167-
168162
@Test
169163
public void defaultInlineTest() throws TransformationException {
170164
String transformation = "|key1=value1;key2=value;=default";
@@ -176,4 +170,22 @@ public void defaultSourceInlineTest() throws TransformationException {
176170
String transformation = "|key1=value1;key2=value;=_source_";
177171
assertEquals("nonexistent", processor.transform(transformation, "nonexistent"));
178172
}
173+
174+
@Test
175+
public void customSeparatorTest() throws TransformationException {
176+
String transformation = "|?delimiter=,key1=value1;with;semicolons,key2;too=value2,?delimiter=value3";
177+
assertEquals("value1;with;semicolons", processor.transform(transformation, "key1"));
178+
assertEquals("value2", processor.transform(transformation, "key2;too"));
179+
assertEquals("value3", processor.transform(transformation, "?delimiter"));
180+
181+
transformation = "|?delimiter=||key1=value1;with;semicolons||key2;too=value2||?delimiter=value3";
182+
assertEquals("value1;with;semicolons", processor.transform(transformation, "key1"));
183+
assertEquals("value2", processor.transform(transformation, "key2;too"));
184+
assertEquals("value3", processor.transform(transformation, "?delimiter"));
185+
186+
transformation = "|key1=value1;key2=value2;?delimiter=value3";
187+
assertEquals("value1", processor.transform(transformation, "key1"));
188+
assertEquals("value2", processor.transform(transformation, "key2"));
189+
assertEquals("value3", processor.transform(transformation, "?delimiter"));
190+
}
179191
}

0 commit comments

Comments
 (0)