/** * @class HebrewAS3 v.1.0 * @author Atarsh * Based on work of Ido Rosenthal * Arranges mixed hebrew-english HTML text for presentation in a dynamic text field. * */ package { import flash.text.TextField; import flash.text.TextFormat; public class HebrewAS3 { private static var instance:HebrewAS3; public static function generateBIDI (_str:String, _txtFld:TextField, _htmlDir:String = "right"):String { if (instance == null) instance = new HebrewAS3(); var arWords:Array = instance.breakToWords(_str); // each cell contains a word or a whole tag as strings arWords = instance.fixWords(arWords); // each cell contains a Word object for the string it had arWords = instance.switchTags(arWords); // switch between opening and closing tags var arBlocks:Array = instance.breakToBlocks(arWords);// D1 contains hebrew or english list as (D2) array arWords = instance.fixBlocksInnerOrder(arBlocks); // contains Words objects ordered for display var arLines:Array = instance.breakLines(arWords, _txtFld); // D1 contains list (D2) of words that fit in one line instance.placeText(arLines, _txtFld, _htmlDir); return _txtFld.htmlText; } /** * breaks a string to words. a tag (< to >) is considered as a single word * @param _str(String) the string to fix * @return an array, each cell holds one word as a string * */ private function breakToWords(_str:String):Array { var regP:RegExp = /<[^>]*>/g; var arWordsAndTags:Array = new Array(); var lastindex:int = 0; var arResult:Object = regP.exec(_str); // matches var innerText:String; // text between tags while (arResult != null){ innerText = _str.substring(lastindex, arResult.index); if(innerText != null && innerText.length > 0) { arWordsAndTags = arWordsAndTags.concat(innerText.split(" ")); } arWordsAndTags.push(arResult[0]) lastindex = regP.lastIndex; arResult = regP.exec(_str); } innerText = _str.substring(lastindex,_str.length); if(innerText != null && innerText.length > 0) { arWordsAndTags = arWordsAndTags.concat(innerText.split(" ")); } return arWordsAndTags; } /** * fixes each word on it's own. * @param _words(Array) the list of words (strings) to fix * @return (Array) array of Word objects. * */ private function fixWords(_words:Array):Array { var fixed:Array = new Array(_words.length); // Word objects var nWords:uint = _words.length; for (var i:int = 0; i 1) { // clear line arLines.push(sTemp); if (word.type == Word.TAG && word.visualText.toLowerCase().indexOf("br") == 1) { sTemp = ""; } else { sTemp = word.visualText; } tf.htmlText = ""; } else { sTemp = word.visualText + " " + sTemp; } } arLines.push(sTemp); return arLines; } /** * puts given lines of text in given textfield * @param _lines (Array) array of fixed lines * @param _txtFld (TextField) textfield to fill * */ private function placeText(_lines:Array, _txtFld:TextField, _htmlDir:String):void { var st:String = '

'; for (var i:uint = 0; i<_lines.length; i++) { st += _lines[i] + "
"; } _txtFld.htmlText = st+"

"; } } } // ======================================================================================= // class Word { // variables: public static const LTR:String = "ltr"; public static const RTL:String = "rtl"; public static const NUMERIC:String = "numeric"; public static const TAG:String = "tag"; public static const ALEF:uint = 0x05D0; public static const TAV:uint = 0x05EA; private var sText:String; // the actual word private var sFixedText:String; // word in correct order for showing private var sType:String; // rtl or ltr // constructor: public function Word(_text:String) { sText = _text; process(); } // methods: /** * decides wether word is LTR or RTL * assigns that value to sType. * sets the string for display * */ private function process():void { var isLTR:Boolean = false; var isRTL:Boolean = false; var isNUMERIC:Boolean = false; var isOTHER:Boolean = false; if (sText.charAt(0) == "<") { // word is tag sType = Word.TAG; sFixedText = sText; } else { var l:int = sText.length; var n:int; for (var i:int =0; i= 65 && n <= 90) || (n >= 97 && n <= 122)) { // english letter isLTR = true; } else if (n >= ALEF && n <= TAV) { // hebrew letter isRTL = true; } else if (n >= 48 && n <= 57) { // number isNUMERIC = true; } else { // panctuation etc. var s:String = sText.charAt(i); if (s != "(" && s != ")" && s != "{" && s != "}" && s != "[" && s != "]") { isOTHER = true; } } } // set type: if (!isRTL && !isLTR && !isNUMERIC) { // only panctuation, treat as hebrew sType = Word.RTL; sFixedText = reverse(sText); switchBrackets(); } else if (!isRTL && !isLTR && isNUMERIC) { // trace("numbers") sType = Word.NUMERIC; sFixedText = sText; } else if (!isRTL && isLTR && !isNUMERIC) { // trace("english") sType = Word.LTR; sFixedText = sText; } else if (!isRTL && isLTR && isNUMERIC) { // trace("english and numbers") sType = Word.LTR; sFixedText = sText; } else if (isRTL && !isLTR && !isNUMERIC) { // trace("hebrew") sType = Word.RTL; sFixedText = reverse(sText); switchBrackets(); } else if (isRTL && !isLTR && isNUMERIC) { // hebrew and numbers // trace("hebrew and numbers") fixComplex(); switchBrackets(); } else if (isRTL && isLTR && !isNUMERIC) { // hebrew and english // trace("hebrew and english") fixComplex(); switchBrackets(); } else if (isRTL && isLTR && isNUMERIC) { // hebrew, english and numbers // trace("hebrew, english and numbers") fixComplex(); switchBrackets(); } } } /** * determines type and fixes string for display * used for complex strings = more than one type * */ private function fixComplex():void { var l:int = sText.length; var n:int; // charcode var s:String; // character var aLetters:Array; var engPointer:int = 0, hebPointer:int = 0; // pointers to last character of type var sLast:String = ""; // last direction aLetters = sText.split(""); for (var i:int = 0; i= 65 && n <= 90) || (n >= 97 && n <= 122) || (n >= 0 && n <= 9)) { if (sType == null) sType = Word.LTR; if (sLast != Word.LTR) { sLast = Word.LTR; } engPointer = i; } else if (n >= ALEF && n <= TAV) { if (sType == null) sType = Word.RTL; if (sLast == Word.LTR) { engPointer = i; sLast = Word.RTL; } else { s = aLetters[i]; aLetters.splice(i, 1); aLetters.splice(engPointer, 0, s); } } } sFixedText = aLetters.join(""); } /** * switches between opening and closing brackets * (), {}, [] * meant for use with hebrew words. * */ private function switchBrackets():void { // normal brackets () if (sFixedText.indexOf("(") != -1) { sFixedText = sFixedText.replace("(", "~ATAR~"); } if (sFixedText.indexOf(")") != -1) { sFixedText = sFixedText.replace(")", "("); } if (sFixedText.indexOf("~ATAR~") != -1) { sFixedText = sFixedText.replace("~ATAR~", ")"); } // curly brackets {} if (sFixedText.indexOf("{") != -1) { sFixedText = sFixedText.replace("{", "~ATAR~"); } if (sFixedText.indexOf("}") != -1) { sFixedText = sFixedText.replace("}", "{"); } if (sFixedText.indexOf("~ATAR~") != -1) { sFixedText = sFixedText.replace("~ATAR~", "}"); } // square brackets [] if (sFixedText.indexOf("[") != -1) { sFixedText = sFixedText.replace("[", "~ATAR~"); } if (sFixedText.indexOf("]") != -1) { sFixedText = sFixedText.replace("]", "["); } if (sFixedText.indexOf("~ATAR~") != -1) { sFixedText = sFixedText.replace("~ATAR~", "]"); } } /** * reverses a string * @param _str (String) string to reverse * @return (String) the reversed string * */ private function reverse(_str:String):String { var newString:String = ""; for (var i:int = _str.length; i>=0; --i) { newString += _str.charAt(i); } return newString; } // getters / setters: public function get logicalText ():String { return this.sText; } public function get visualText():String { return this.sFixedText; } public function get type():String { return this.sType; } }