Fix escaping in yaml strings (#1165)

The backslash needs to be escaped when rendering double-quoted YAML strings.

In addition, this escapes the following characters:

* next line (0x85)
* nbsp (0xa0)
This commit is contained in:
Daniel Chao
2025-08-06 07:57:52 -07:00
committed by GitHub
parent 78ba6bf758
commit 7d50aaeec9
4 changed files with 82 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,33 +19,55 @@ import org.pkl.core.util.AbstractCharEscaper;
import org.pkl.core.util.IoUtils;
import org.pkl.core.util.Nullable;
// https://yaml.org/spec/1.2.2/#57-escaped-characters
/**
* Emits escape sequences for YAML. This is only used when emitting double-quoted strings.
*
* <p>Note: we don't need to escape space ({@code 0x20}) because we don't generate quoted multiline
* strings. We also don't need to escape forward slash ({@code 0x2f}) because a normal forward slash
* is also valid YAML.
*
* @see <a
* href="https://yaml.org/spec/1.2.2/#57-escaped-characters">https://yaml.org/spec/1.2.2/#57-escaped-characters</a>
*/
public final class YamlEscaper extends AbstractCharEscaper {
private static final String[] REPLACEMENTS;
static {
REPLACEMENTS = new String[0x22 + 1];
REPLACEMENTS = new String[0xA0 + 1];
for (var i = 0; i < 0x20; i++) {
REPLACEMENTS[i] = IoUtils.toHexEscape(i);
}
// ns-esc-null
REPLACEMENTS[0x00] = "\\0";
// ns-esc-bell
REPLACEMENTS[0x07] = "\\a";
// ns-esc-backspace
REPLACEMENTS[0x08] = "\\b";
// ns-esc-horizontal-tab
REPLACEMENTS[0x09] = "\\t";
// ns-esc-line-feed
REPLACEMENTS[0x0A] = "\\n";
// ns-esc-vertical-tab
REPLACEMENTS[0x0B] = "\\v";
// ns-esc-form-feed
REPLACEMENTS[0x0C] = "\\f";
// ns-esc-carriage-return
REPLACEMENTS[0x0D] = "\\r";
// ns-esc-escape
REPLACEMENTS[0x1B] = "\\e";
// we don't ever need to escape 0x20 because we don't generate quoted multiline strings
// ns-esc-double-quote
REPLACEMENTS[0x22] = "\\\"";
// ns-esc-backslash
REPLACEMENTS[0x5c] = "\\\\";
// ns-esc-next-line
REPLACEMENTS[0x85] = "\\N";
// ns-esc-non-breaking-space
REPLACEMENTS[0xA0] = "\\_";
}
@Override
protected @Nullable String findReplacement(char ch) {
//noinspection UnnecessaryUnicodeEscape
return ch <= '\u0022'
? REPLACEMENTS[ch]
: ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null;
return ch <= 0xA0 ? REPLACEMENTS[ch] : ch == '\u2028' ? "\\L" : ch == '\u2029' ? "\\P" : null;
}
}

View File

@@ -0,0 +1,36 @@
// test escaping in double quotes
// every string is prefixed with `\t` s.t. YamlRenderer will emit double-quoted strings.
`null` = "\t\u{00}"
bell = "\t\u{7}"
backspace = "\t\u{8}"
horizontalTab = "\t"
lineFeed = "\t\u{a}"
verticalTab = "\t\u{b}"
formFeed = "\t\u{c}"
carriageReturn = "\t\r"
escape = "\t\u{1b}"
doubleQuote = "\t\""
backslash = "\t\\"
nextLine = "\t\u{85}"
nbsp = "\t\u{a0}"
lineSep = "\t\u{2028}"
paragraphSep = "\t\u{2029}"
output {
renderer = new YamlRenderer {}
}

View File

@@ -114,12 +114,12 @@ examples {
render(".NAN")
render(".nAn") // never float
}
["tag like strings"] {
"!!bool true"
"!!str my string value"
}
["number like string keys"] {
render(new Dynamic {
`0` = "0"

View File

@@ -0,0 +1,15 @@
'null': "\t\0"
bell: "\t\a"
backspace: "\t\b"
horizontalTab: "\t"
lineFeed: "\t\n"
verticalTab: "\t\v"
formFeed: "\t\f"
carriageReturn: "\t\r"
escape: "\t\e"
doubleQuote: "\t\""
backslash: "\t\\"
nextLine: "\t\N"
nbsp: "\t\_"
lineSep: "\t\L"
paragraphSep: "\t\P"