Convert escaped seqs properly in YAML dq strings

This commit is contained in:
Valentin Fondaratov
2016-07-15 19:33:40 +03:00
parent 8640a09ed0
commit b040862e2b
4 changed files with 119 additions and 25 deletions
@@ -1,9 +1,12 @@
package org.jetbrains.yaml.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.YAMLTokenTypes;
import org.jetbrains.yaml.YAMLUtil;
@@ -12,6 +15,7 @@ import org.jetbrains.yaml.psi.YAMLQuotedText;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText {
private final boolean myIsSingleQuoted;
@@ -73,32 +77,29 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText
}
}
@SuppressWarnings("AssignmentToForLoopParameter")
@Override
protected List<Pair<TextRange, String>> getDecodeReplacements(@NotNull CharSequence input) {
List<Pair<TextRange, String>> result = new ArrayList<>();
for (int i = 0; i + 1 < input.length(); ++i) {
final CharSequence subSequence = input.subSequence(i, i + 2);
final TextRange textRange = TextRange.create(i, i + 2);
if (isSingleQuote() && "''".equals(subSequence)) {
result.add(Pair.create(textRange, "'"));
if (isSingleQuote() && input.charAt(i) == '\'' && input.charAt(i + 1) == '\'') {
result.add(Pair.create(TextRange.from(i, 2), "'"));
i++;
}
else if (!isSingleQuote() && "\\\n".equals(subSequence)) {
result.add(Pair.create(textRange, ""));
else if (!isSingleQuote() && input.charAt(i) == '\\') {
if (input.charAt(i + 1) == '\n') {
result.add(Pair.create(TextRange.from(i, 2), ""));
i++;
continue;
}
final int length = Escaper.findEscapementLength(input, i);
final int charCode = Escaper.toUnicodeChar(input, i, length);
final TextRange range = TextRange.create(i, Math.min(i + length + 1, input.length()));
result.add(Pair.create(range, Character.toString((char)charCode)));
i += range.getLength() - 1;
}
else if (!isSingleQuote() && "\\ ".equals(subSequence)) {
result.add(Pair.create(textRange, " "));
}
else if (!isSingleQuote() && "\\\"".equals(subSequence)) {
result.add(Pair.create(textRange, "\""));
}
else {
//noinspection AssignmentToForLoopParameter
i--;
}
//noinspection AssignmentToForLoopParameter
i++;
}
return result;
}
@@ -120,9 +121,10 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText
final List<Pair<TextRange, String>> result = new ArrayList<>();
int currentLength = 0;
for (int i = 0; i < input.length(); ++i) {
if (input.charAt(i) == '\n') {
final char c = input.charAt(i);
if (c == '\n') {
if (!isSingleQuote() && i + 1 < input.length() && YAMLGrammarCharUtil.isSpaceLike(input.charAt(i + 1))) {
result.add(Pair.create(TextRange.from(i, 1), "\\\n" + indentString + "\\"));
result.add(Pair.create(TextRange.from(i, 1), "\\n\\\n" + indentString + "\\"));
}
else {
result.add(Pair.create(TextRange.from(i, 1), "\n\n" + indentString));
@@ -133,12 +135,12 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText
if (currentLength > MAX_SCALAR_LENGTH_PREDEFINED
&& (!isSingleQuote() || (input.charAt(i) == ' ' && isSurroundedByNoSpace(input, i)))) {
&& (!isSingleQuote() || (c == ' ' && isSurroundedByNoSpace(input, i)))) {
final String replacement;
if (isSingleQuote()) {
replacement = "\n" + indentString;
}
else if (YAMLGrammarCharUtil.isSpaceLike(input.charAt(i))) {
else if (YAMLGrammarCharUtil.isSpaceLike(c)) {
replacement = "\\\n" + indentString + "\\";
}
else {
@@ -150,16 +152,16 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText
currentLength++;
if (isSingleQuote() && input.charAt(i) == '\'') {
if (isSingleQuote() && c == '\'') {
result.add(Pair.create(TextRange.from(i, 1), "''"));
continue;
}
if (!isSingleQuote()) {
if (input.charAt(i) == '"') {
if (c == '"') {
result.add(Pair.create(TextRange.from(i, 1), "\\\""));
}
else if (input.charAt(i) == '\\') {
else if (c == '\\') {
result.add(Pair.create(TextRange.from(i, 1), "\\\\"));
}
}
@@ -180,4 +182,87 @@ public class YAMLQuotedTextImpl extends YAMLScalarImpl implements YAMLQuotedText
public String toString() {
return "YAML quoted text";
}
private static class Escaper {
private static final int[][] ONE_LETTER_CONVERSIONS = new int[][] {
{'0', 0},
{'a', 7},
{'b', 8},
{'t', 9},
{9, 9},
{'n', 10},
{'v', 11},
{'f', 12},
{'r', 13},
{'e', 27},
{' ', 32},
{'"', 34},
{'/', 47},
{'\\', 92},
{'N', 133},
{'_', 160},
{'L', 8232},
{'P', 8233},
};
private static final NotNullLazyValue<Map<Integer, Integer>> ESC_TO_CODE = new NotNullLazyValue<Map<Integer, Integer>>() {
@NotNull
@Override
protected Map<Integer, Integer> compute() {
final HashMap<Integer, Integer> map = new HashMap<>(ONE_LETTER_CONVERSIONS.length);
for (int[] conversion : ONE_LETTER_CONVERSIONS) {
map.put(conversion[0], conversion[1]);
}
return map;
}
};
private static final NotNullLazyValue<Map<Integer, Integer>> CODE_TO_ESC = new NotNullLazyValue<Map<Integer, Integer>>() {
@NotNull
@Override
protected Map<Integer, Integer> compute() {
final HashMap<Integer, Integer> map = new HashMap<>(ONE_LETTER_CONVERSIONS.length);
for (int[] conversion : ONE_LETTER_CONVERSIONS) {
map.put(conversion[1], conversion[2]);
}
return map;
}
};
static int findEscapementLength(@NotNull CharSequence text, int pos) {
if (pos + 1 >= text.length() || text.charAt(pos) != '\\') {
throw new IllegalArgumentException("This is not an escapement start");
}
final char c = text.charAt(pos + 1);
if (c == 'x') {
return 3;
}
else if (c == 'u') {
return 5;
}
else if (c == 'U') {
return 9;
}
else {
return 1;
}
}
static int toUnicodeChar(@NotNull CharSequence text, int pos, int length) {
if (length > 1) {
CharSequence s = text.subSequence(pos + 2, Math.min(text.length(), pos + length + 1));
try {
return Integer.parseInt(s.toString(), 16);
}
catch (NumberFormatException e) {
return (int)'?';
}
}
else {
final Integer result = ESC_TO_CODE.getValue().get((int)text.charAt(pos + 1));
return ObjectUtils.notNull(result, (int)text.charAt(pos + 1));
}
}
}
}
@@ -73,6 +73,10 @@ public class YAMLScalarContentTest extends LightPlatformCodeInsightFixtureTestCa
public void testDoubleQuote2() {
doTest();
}
public void testDoubleQuote4() {
doTest();
}
private void doTest() {
myFixture.configureByFile(getTestName(true) + ".yml");
@@ -0,0 +1,5 @@
"\n\n\nFun with \\
\" \a \b \e \f \
\n \r \t \v \0 \
\ \_ \N \L \P \
\x41 \u0041 \U00000041"