Time after time we run into the same problem on different platforms, with different languages. The problem's name is "Visual to Logical conversion for right-to-left or bidirectional text". The problem is usually due to legacy code, which stores texts in visual order from left to right. In case of English it's ok, but with Hebrew this means that texts are partially reversed.
It worth to note that we've solved the same task with Windows API for native and .NET applications more than 10 years ago.
On the other hand, for Java, we yet didn't see any acceptable standalone solution. To remedy this omission, we publish here our solution to this problem.
package com.nesterovskyBros.text; import java.text.Bidi; /** * Utility that uses {@link Bidi} class. */ public class BidiUtils { /** * Implements visual to logical order converter. * * @author <a href="http://www.nesterovsky-bros.com">Nesterovsky bros</a> * * @param text an input text in visual order to convert. * @return a String value in logical order. */ public static String visualToLogical(String text) { if ((text == null) || (text.length() == 0)) { return text; } Bidi bidi = new Bidi(text, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); if (bidi.isLeftToRight()) { return text; } int count = bidi.getRunCount(); byte[] levels = new byte[count]; Integer[] runs = new Integer[count]; for (int i = 0; i < count; i++) { levels[i] = (byte)bidi.getRunLevel(i); runs[i] = i; } Bidi.reorderVisually(levels, 0, runs, 0, count); StringBuilder result = new StringBuilder(); for (int i = 0; i < count; i++) { int index = runs[i]; int start = bidi.getRunStart(index); int end = bidi.getRunLimit(index); int level = levels[index]; if ((level & 1) != 0) { for (; --end >= start;) { result.append(text.charAt(end)); } } else { result.append(text, start, end); } } return result.toString(); } }
This method utilizes embeded Bidi's algorithm, see class java.text.Bidi.
Be aware that there is no perfect algorithm that covers all possible cases, since BIDI was written for an opposite task, but our implementation based on Bidi.reorderVisually is usually acceptable.
Bidi.reorderVisually
Here is an JUnit test for this method:
package com.nesterovskyBors.text; import static org.junit.Assert.*; import org.junit.Test; import com.nesterovskyBros.text.BidiUtils; public class BidiUtilsTests { @Test public void testsVisualToLogical() { String text = "123 יתימאה ןחבמ"; String actual = BidiUtils.visualToLogical(text); String expected = "מבחן האמיתי 123"; assertEquals(expected, actual); text = "תירבע English תירבע בוש"; actual = BidiUtils.visualToLogical(text); expected = "שוב עברית English עברית"; assertEquals(expected, actual); } }