Skip to content

fix: escape double-quotes and backslashes in device attributes#30746

Merged
Koenkk merged 5 commits intoKoenkk:devfrom
stephanGarland:dev
Jan 27, 2026
Merged

fix: escape double-quotes and backslashes in device attributes#30746
Koenkk merged 5 commits intoKoenkk:devfrom
stephanGarland:dev

Conversation

@stephanGarland
Copy link
Copy Markdown
Contributor

Some devices, like Philips Hue Downlights, have special characters in them e.g. " (meaning the unit of measurement, "inch"). This breaks .dot files upon generation.

This PR escapes those, and backslashes, should they exist elsewhere.

Some devices, like Philips Hue Downlights, have special characters in them e.g. `"` (meaning the unit of measurement, "inch"). This breaks .dot files upon generation.

This PR escapes those, and backslashes, should they exist elsewhere.
@Nerivec
Copy link
Copy Markdown
Collaborator

Nerivec commented Jan 22, 2026

We might want a CI test for this, if it's a breaking pattern for graphviz.

@stephanGarland
Copy link
Copy Markdown
Contributor Author

Fair enough. I’m not a JS/TS programmer, but I can take a look at the test setup and see what I can do. It might be a while though, as my area is supposed to get clobbered with snow and ice this weekend, so I’m prepping for that.

@Nerivec
Copy link
Copy Markdown
Collaborator

Nerivec commented Jan 22, 2026

I think a copy of this with device.definition.description altered (make sure to revert the change at end of test though):

it("Output graphviz networkmap", async () => {
mock();
const device = devices.bulb_color;
device.lastSeen = undefined;
const endpoint = device.getEndpoint(1);
const data = {modelID: "test"};
const payload = {data, cluster: "genOnOff", device, endpoint, type: "readResponse", linkquality: 10};
await mockZHEvents.message(payload);
mockMQTTEvents.message("zigbee2mqtt/bridge/request/networkmap", stringify({type: "graphviz", routes: true}));
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual("zigbee2mqtt/bridge/response/networkmap");
const expected = `digraph G {
node[shape=record];
"0x00124b00120144ae" [style="bold, filled", fillcolor="#e04e5d", fontcolor="#ffffff", label="{Coordinator|0x00124b00120144ae (0x0000)|0 seconds ago}"];
"0x000b57fffec6a5b2" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb|0x000b57fffec6a5b2 (0x9db1)|IKEA TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm (LED1545G12)|9 seconds ago}"];
"0x000b57fffec6a5b2" -> "0x00124b00120144ae" [penwidth=2, weight=1, color="#009900", label="92 (routes: 0x198c)"]
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (0x9dcf)|Philips Hue Go (7146060PH)|unknown}"];
"0x000b57fffec6a5b3" -> "0x00124b00120144ae" [penwidth=0.5, weight=0, color="#994444", label="120"]
"0x000b57fffec6a5b3" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="110"]
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (0x198a)|Aqara Wireless remote switch (double rocker), 2016 model (WXKG02LM_rev1)|9 seconds ago}"];
"0x0017880104e45521" -> "0x0017880104e45559" [penwidth=1, weight=0, color="#994444", label="130"]
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (0x1988)failed: lqi,routingTable|Boef Automatically generated definition (notSupportedModelID)|9 seconds ago}"];
"0x0017880104e45559" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{cc2530_router|0x0017880104e45559 (0x198c)|Custom devices (DiY) CC2530 router (CC2530.ROUTER)|9 seconds ago}"];
"0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"]
"0x0017880104e45511" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{0x0017880104e45511|0x0017880104e45511 (0x045a)|external external/converter (external_converter_device)|9 seconds ago}"];
"0x0017880104e45511" -> "0x00124b00120144ae" [penwidth=1, weight=0, color="#994444", label="92"]
}`;
const expectedLines = expected.split("\n");
const actualLines = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).data.value.split("\n");
for (let i = 0; i < expectedLines.length; i++) {
expect(actualLines[i].trim()).toStrictEqual(expectedLines[i].trim());
}
});

@stephanGarland
Copy link
Copy Markdown
Contributor Author

stephanGarland commented Jan 25, 2026

As mentioned, I am not a JS/TS programmer (100% ChatGPT work for this test) so if you want any of this changed, let me know, but otherwise I think it should be good to go.

EDIT: Well, shit. It passed locally.

 Test Files  23 passed (23)
      Tests  722 passed (722)
   Start at  19:18:40
   Duration  4.68s (transform 1.68s, setup 0ms, collect 10.29s, tests 19.16s, environment 2ms, prepare 1.06s)

EDIT2: It passes on CI now as well, I see - not sure what the issue was before.

@Koenkk Koenkk merged commit 5de47a0 into Koenkk:dev Jan 27, 2026
11 checks passed
@Koenkk
Copy link
Copy Markdown
Owner

Koenkk commented Jan 27, 2026

Thanks!

Koenkk added a commit that referenced this pull request Feb 3, 2026
…ibutes (#30746)

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants