From cbdc9c82e00edb9496f41a4aca3d12ef7ee8321b Mon Sep 17 00:00:00 2001 From: Uladzislau Arlouski Date: Fri, 14 Dec 2018 16:48:22 +0300 Subject: [PATCH] Add support for pattern flags --- .../internal/filter/FilterCompiler.java | 7 ++- .../jsonpath/internal/filter/PatternFlag.java | 48 +++++++++++++++++++ .../jsonpath/internal/filter/ValueNodes.java | 11 ++--- .../internal/filter/PatternFlagTest.java | 47 ++++++++++++++++++ .../internal/filter/RegexpEvaluatorTest.java | 30 ++++++++---- 5 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/filter/PatternFlagTest.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java index 107d2364..6fcb5a71 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java @@ -280,8 +280,11 @@ public class FilterCompiler { if (closingIndex == -1) { throw new InvalidPathException("Pattern not closed. Expected " + PATTERN + " in " + filter); } else { - if(filter.inBounds(closingIndex+1) && filter.charAt(closingIndex+1) == IGNORE_CASE){ - closingIndex++; + if(filter.inBounds(closingIndex+1)) { + int equalSignIndex = filter.nextIndexOf('='); + int endIndex = equalSignIndex > closingIndex ? equalSignIndex : filter.nextIndexOfUnescaped(CLOSE_PARENTHESIS); + CharSequence flags = filter.subSequence(closingIndex + 1, endIndex); + closingIndex += flags.length(); } filter.setPosition(closingIndex + 1); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java new file mode 100644 index 00000000..3ac05128 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PatternFlag.java @@ -0,0 +1,48 @@ +package com.jayway.jsonpath.internal.filter; + +import java.util.regex.Pattern; + +public enum PatternFlag { + UNIX_LINES(Pattern.UNIX_LINES, 'd'), + CASE_INSENSITIVE(Pattern.CASE_INSENSITIVE, 'i'), + COMMENTS(Pattern.COMMENTS, 'x'), + MULTILINE(Pattern.MULTILINE, 'm'), + DOTALL(Pattern.DOTALL, 's'), + UNICODE_CASE(Pattern.UNICODE_CASE, 'u'), + UNICODE_CHARACTER_CLASS(Pattern.UNICODE_CHARACTER_CLASS, 'U'); + + private final int code; + private final char flag; + + private PatternFlag(int code, char flag) { + this.code = code; + this.flag = flag; + } + + public static int parseFlags(char[] flags) { + int flagsValue = 0; + for (char flag : flags) { + flagsValue |= getCodeByFlag(flag); + } + return flagsValue; + } + + public static String parseFlags(int flags) { + StringBuilder builder = new StringBuilder(); + for (PatternFlag patternFlag : PatternFlag.values()) { + if ((patternFlag.code & flags) == patternFlag.code) { + builder.append(patternFlag.flag); + } + } + return builder.toString(); + } + + private static int getCodeByFlag(char flag) { + for (PatternFlag patternFlag : PatternFlag.values()) { + if (patternFlag.flag == flag) { + return patternFlag.code; + } + } + return 0; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java index 531868db..c4030bc7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNodes.java @@ -47,19 +47,22 @@ public interface ValueNodes { class PatternNode extends ValueNode { private final String pattern; private final Pattern compiledPattern; + private final String flags; PatternNode(CharSequence charSequence) { String tmp = charSequence.toString(); int begin = tmp.indexOf('/'); int end = tmp.lastIndexOf('/'); - int flags = tmp.endsWith("/i") ? Pattern.CASE_INSENSITIVE : 0; this.pattern = tmp.substring(begin + 1, end); - this.compiledPattern = Pattern.compile(pattern, flags); + int flagsIndex = end + 1; + this.flags = tmp.length() > flagsIndex ? tmp.substring(flagsIndex) : ""; + this.compiledPattern = Pattern.compile(pattern, PatternFlag.parseFlags(flags.toCharArray())); } PatternNode(Pattern pattern) { this.pattern = pattern.pattern(); this.compiledPattern = pattern; + this.flags = PatternFlag.parseFlags(pattern.flags()); } @@ -83,10 +86,6 @@ public interface ValueNodes { @Override public String toString() { - String flags = ""; - if((compiledPattern.flags() & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE){ - flags = "i"; - } if(!pattern.startsWith("/")){ return "/" + pattern + "/" + flags; } else { diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/filter/PatternFlagTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/PatternFlagTest.java new file mode 100644 index 00000000..c83d80dd --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/PatternFlagTest.java @@ -0,0 +1,47 @@ +package com.jayway.jsonpath.internal.filter; + +import com.jayway.jsonpath.BaseTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; + +@RunWith(Parameterized.class) +public class PatternFlagTest extends BaseTest { + + private final int flags; + private final String expectedFlags; + + public PatternFlagTest(int flags, String expectedFlags) { + this.flags = flags; + this.expectedFlags = expectedFlags; + } + + @Test + public void testParseFlags() { + Assert.assertEquals(expectedFlags, PatternFlag.parseFlags(flags)); + } + + @Parameterized.Parameters + public static Iterable data() { + return Arrays.asList( + new Object[][]{ + { 1, "d" }, + { 2, "i" }, + { 4, "x" }, + { 8, "m" }, + { 32, "s" }, + { 64, "u" }, + { 256, "U" }, + { 300, "xmsU" }, + { 13, "dxm" }, + { 7, "dix" }, + { 100, "xsu" }, + { 367, "dixmsuU" } + } + ); + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/filter/RegexpEvaluatorTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/RegexpEvaluatorTest.java index 614f6b6c..0bb61f1f 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/filter/RegexpEvaluatorTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/RegexpEvaluatorTest.java @@ -49,14 +49,28 @@ public class RegexpEvaluatorTest extends BaseTest { public static Iterable data() { return Arrays.asList( new Object[][]{ - { "/true|false/", createStringNode("true", true), true }, - { "/9.*9/", createNumberNode("9979"), true }, - { "/fa.*se/", createBooleanNode("false"), true }, - { "/Eval.*or/", createClassNode(String.class), false }, - { "/JsonNode/", createJsonNode(json()), false }, - { "/PathNode/", createPathNode(path()), false }, - { "/Undefined/", createUndefinedNode(), false }, - { "/NullNode/", createNullNode(), false } + { "/true|false/", createStringNode("true", true), true }, + { "/9.*9/", createNumberNode("9979"), true }, + { "/fa.*se/", createBooleanNode("false"), true }, + { "/Eval.*or/", createClassNode(String.class), false }, + { "/JsonNode/", createJsonNode(json()), false }, + { "/PathNode/", createPathNode(path()), false }, + { "/Undefined/", createUndefinedNode(), false }, + { "/NullNode/", createNullNode(), false }, + { "/test/i", createStringNode("tEsT", true), true }, + { "/test/", createStringNode("tEsT", true), false }, + { "/\u00de/ui", createStringNode("\u00fe", true), true }, + { "/\u00de/", createStringNode("\u00fe", true), false }, + { "/\u00de/i", createStringNode("\u00fe", true), false }, + { "/test# code/", createStringNode("test", true), false }, + { "/test# code/x", createStringNode("test", true), true }, + { "/.*test.*/d", createStringNode("my\rtest", true), true }, + { "/.*test.*/", createStringNode("my\rtest", true), false }, + { "/.*tEst.*/is", createStringNode("test\ntest", true), true }, + { "/.*tEst.*/i", createStringNode("test\ntest", true), false }, + { "/^\\w+$/U", createStringNode("\u00fe", true), true }, + { "/^\\w+$/", createStringNode("\u00fe", true), false }, + { "/^test$\\ntest$/m", createStringNode("test\ntest", true), true } } ); }