1. opensymphony
  2. xwork

Commits

Charles Miller  committed ef67532

CONF-30205 - add a bunch of tests for the TextUtilParse
rewrite TextUtilParse to do paren matching properly

  • Participants
  • Parent commits 04eb4aa
  • Branches xwork_1-0-3, xwork_1-0-3_branch

Comments (0)

Files changed (2)

File src/java/com/opensymphony/xwork/util/TextParseUtil.java

View file
  • Ignore whitespace
  */
 package com.opensymphony.xwork.util;
 
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Utility class for text parsing.
  *
     //~ Methods ////////////////////////////////////////////////////////////////
 
     /**
-     * Converts all instances of ${...} in <code>expression</code> to the value returned
+     * Converts all instances of ${...} in <code>string</code> to the value returned
      * by a call to {@link OgnlValueStack#findValue(java.lang.String)}. If an item cannot
      * be found on the stack (null is returned), then the entire variable ${...} is not
      * displayed, just as if the item was on the stack but returned an empty string.
      *
-     * @param expression an expression that hasn't yet been translated
-     * @return the parsed expression
+     * @param string a string that may contain ognl expressions
+     * @return the parsed string
      */
-    public static String translateVariables(String expression, final OgnlValueStack stack) {
-        StringBuilder sb = new StringBuilder();
-        Pattern p = Pattern.compile("\\$\\{([^}]*)\\}");
-        Matcher m = p.matcher(expression);
-        int previous = 0;
-        while (m.find()) {
-            String g = m.group(1);
-            int start = m.start();
-            String value;
-            try {
-                Object o = stack.findValue(g);
-                value = o == null ? "" : o.toString();
-            } catch (Exception ignored) {
-                value = "";
+    public static String translateVariables(String string, final OgnlValueStack stack) {
+        if (string == null)
+            return null;
+        
+        boolean lastWasDollar = false;
+        int parenCount = 0;
+        StringBuilder out = new StringBuilder();
+        StringBuilder expression = new StringBuilder();
+
+        for (int i = 0; i < string.length(); i++)
+        {
+            if (Character.isLowSurrogate(string.charAt(i)))
+                continue;
+
+            boolean maybeExpression = lastWasDollar;
+            lastWasDollar = false;
+
+            int ch = string.codePointAt(i);
+
+            if (maybeExpression && ch != '{')
+                out.append('$');
+
+            if (ch == '$' && parenCount == 0) {
+                lastWasDollar = true;
+            } else if (ch == '{') {
+                if (maybeExpression || parenCount > 0) {
+                    parenCount++;
+
+                    if (parenCount > 1)
+                        expression.appendCodePoint(ch);
+                } else {
+                    out.appendCodePoint(ch);
+                }
+            } else if (ch == '}' && parenCount > 0) {
+                parenCount--;
+
+                if (parenCount == 0) {
+                    out.append(executeExpression(stack, expression.toString()));
+                    expression.setLength(0);
+                } else {
+                    expression.appendCodePoint(ch);
+                }
+            } else if (parenCount > 0) {
+                expression.appendCodePoint(ch);
+            } else {
+                out.appendCodePoint(ch);
             }
-            sb.append(expression.substring(previous, start)).append(value);
-            previous = m.end();
-        }
-        if (previous < expression.length() ) {
-            sb.append(expression.substring(previous));
         }
-        return sb.toString();
+
+        if (lastWasDollar)
+            out.append("$");
+
+        if (parenCount > 0)
+            out.append("${");
+
+        out.append(expression);
+
+        return out.toString();
+    }
+
+    private static String executeExpression(OgnlValueStack stack, String expression) {
+        Object o = stack.findValue(expression);
+        return o == null ? "" : o.toString();
     }
 }

File src/test/com/opensymphony/xwork/util/TextParseUtilTest.java

View file
  • Ignore whitespace
 import junit.framework.TestCase;
 
 public class TextParseUtilTest extends TestCase {
-    public void testTranslateVariablesDoesNotParseUserSuppliedOgnlExpressions() throws Exception {
+
+    public void testComplexExpression() throws Exception {
+        final OgnlValueStack stack = newStack();
+        testExpression(stack, "a${name}b${hates.name}${}${fish}${age}${age}${a", "a${age}\\0b${name}1010${a");
+    }
+
+    public void testTranslateNull()
+    {
+        testExpression(newStack(), null, null);
+    }
+
+    public void testTranslateEmptyString()
+    {
+        testExpression(newStack(), "", "");
+    }
+
+    private void testExpression(OgnlValueStack stack, String expression, String expectedResult) {
+        assertEquals(expectedResult, TextParseUtil.translateVariables(expression, stack));
+    }
+
+    public void testTranslateNoVarsString()
+    {
+        testExpression(newStack(), "I am the happiest fish in the world", "I am the happiest fish in the world");
+    }
+
+    public void testTranslateUnterminatedVar()
+    {
+        testExpression(newStack(), "${", "${");
+        testExpression(newStack(), "${a", "${a");
+        testExpression(newStack(), "}$", "}$");
+        testExpression(newStack(), "}", "}");
+        testExpression(newStack(), "$ {a}", "$ {a}");
+        testExpression(newStack(), "$", "$");
+        testExpression(newStack(), "{", "{");
+    }
+
+    public void testTranslateSimpleVar()
+    {
+        testExpression(newStack(), "${age}", "10");
+        testExpression(newStack(), "asdf${age}", "asdf10");
+        testExpression(newStack(), "${age}asdf", "10asdf");
+        testExpression(newStack(), "asdf${age}asdf", "asdf10asdf");
+    }
+
+    public void testTranslateSameVarTwice()
+    {
+        testExpression(newStack(), "${age}${age}", "1010");
+    }
+
+    public void testDumbStuff()
+    {
+        testExpression(newStack(), "$${age}{${age}}", "$10{10}");
+    }
+
+    public void testVarThatReturnsAnnoyingRegexEscape()
+    {
+        testExpression(newStack(), "${name}", "${age}\\0");
+    }
+
+    public void testPreservesUnicodeGoldfish()
+    {
+        OgnlValueStack stack = new OgnlValueStack();
+        final Dog dog = new Dog();
+        String fishy = "\uD83D\uDC20";
+        dog.setName("Fishy: " + fishy);
+        stack.push(dog);
+
+        testExpression(stack, fishy + "${name}" + fishy, fishy + "Fishy: " + fishy + fishy);
+    }
+
+    public void testTranslateVarThatReturnsSameVarLater()
+    {
+        OgnlValueStack stack = new OgnlValueStack();
+        final Dog dog = new Dog();
+        dog.setName("I am a ${name}");
+        stack.push(dog);
+        testExpression(stack, "I am a ${name}, yes I am", "I am a I am a ${name}, yes I am");
+    }
+
+    public void testBullshitOgnlStuff()
+    {
+        testExpression(newStack(), "foo: ${{1, 2, 3}}", "foo: [1, 2, 3]");
+        testExpression(newStack(), "foo: ${#{1 : 2, 3 : 4}}", "foo: {1=2, 3=4}");
+    }
+
+    public void testNulls()
+    {
+        OgnlValueStack stack = new OgnlValueStack();
+        final Dog dog = new Dog();
+        dog.setName("\0cheese\0");
+        stack.push(dog);
+        testExpression(stack, "\0${name}\0", "\0\0cheese\0\0");
+        testExpression(stack, "${na\0me}", "");
+    }
+
+    private OgnlValueStack newStack() {
         final OgnlValueStack stack = new OgnlValueStack();
         final Dog dog = new Dog();
         final Cat cat = new Cat();
         dog.setAge(10);
         dog.setHates(cat);
         stack.push(dog);
-        String result = TextParseUtil.translateVariables("a${name}b${hates.name}${}${fish}${age}${age}${a", stack);
-        assertEquals("a${age}\\0b${name}1010${a", result);
+        return stack;
     }
+
 }