BidiUtils.js 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  1. // Licensed Materials - Property of IBM
  2. // IBM Cognos Products: cclcore
  3. // (C) Copyright IBM Corp. 2012
  4. // US Government Users Restricted Rights – Use, duplication or disclosure restricted
  5. // by GSA ADP Schedule Contract with IBM Corp.
  6. //
  7. // Common bidi javascript library - BidiUtils.
  8. // Singleton containing useable bidi methods.
  9. function BidiUtils(){
  10. this.baseTextDirection = "ltr";
  11. this.guiOrientation = "ltr";
  12. this.productLocale = "en";
  13. this.LRM = '\u200E';
  14. this.RLM = '\u200F';
  15. this.LRE = '\u202A';
  16. this.RLE = '\u202B';
  17. this.PDF = '\u202C';
  18. this.ZWNJ= '\u200C';
  19. this.BACKREF = '$1';
  20. this.BACKREF_PDF = '\u202C$1';
  21. this.caretPos = null;
  22. this.LTR_DIR=1;
  23. this.RTL_DIR=2;
  24. this.multiemail="multiemail";
  25. this._K_BACK = 0x8;
  26. this._K_SHIFT = 0x10;
  27. this._K_END = 0x23;
  28. this._K_HOME = 0x24;
  29. this._K_LEFT = 0x25;
  30. this._K_RIGHT = 0x27;
  31. this._K_DELETE = 0x2e;
  32. this._K_PAGEUP = 0x21;
  33. this._K_PAGEDOWN = 0x22;
  34. this._K_UP = 0x26;
  35. this._K_DOWN = 0x28;
  36. this._K_ENTER = 0xD;
  37. this._K_ESC = 0x1B;
  38. this._K_INSERT = 0x2D;
  39. this.isIE = navigator.userAgent.indexOf("MSIE") >=0;
  40. this.isFF = navigator.userAgent.toLowerCase().indexOf("firefox") >=0;
  41. };
  42. BidiUtils.instance = null;
  43. // This function returns the singleton instance of BidiUtils.
  44. BidiUtils.getInstance = function(){
  45. if (BidiUtils.instance == null){
  46. BidiUtils.instance = new BidiUtils();
  47. }
  48. return BidiUtils.instance;
  49. };
  50. // The function is used to set properties, needed for correct work of singleton methods.
  51. // @bTextDir: base text direction
  52. // @orient: orientation of the current page
  53. // @pLocale: the locale (product language)
  54. BidiUtils.prototype.setProperties = function(bTextDir, orient, pLocale){
  55. if (bTextDir){
  56. this.baseTextDirection = bTextDir;
  57. }
  58. if (orient){
  59. this.guiOrientation = orient;
  60. }
  61. if (pLocale){
  62. this.productLocale = pLocale;
  63. }
  64. };
  65. // The function is used to add bidi specific handler for focus event, which in its turn
  66. // is used to add all event handlers, needed for given element
  67. // in accordance with its type and specifics.
  68. // @objOrId: element or its id.
  69. BidiUtils.prototype.attachAllListeners = function(objOrId){
  70. var object = document.getElementById(objOrId);
  71. this._patchMethod(object,"onfocus");
  72. };
  73. // The function is the bidi-specific handler for focus event.
  74. // First call of this function allows to add all event handlers, needed for given element
  75. // in accordance with its type and specifics.
  76. // @object: element, whose onfocus handler should be added or updated
  77. BidiUtils.prototype.onfocus = function(object,event){
  78. if (!object.isHandled){
  79. object.isHandled = true;
  80. if (object.attributes["stttype"]){
  81. object.stttype = object.attributes["stttype"].nodeValue;
  82. }
  83. this._attachListeners(object);
  84. }
  85. };
  86. // The function is used to add for given element all bidi-specific handlers.
  87. // @object: element that we need to add bidi-specific handlers
  88. BidiUtils.prototype._attachListeners = function(object){
  89. if (!object.stttype){
  90. if (this.baseTextDirection == "auto"){
  91. this._patchMethod(object,"onkeyup");
  92. }
  93. }
  94. else {
  95. if (typeof this["_parse_" + object.stttype] !== "function"){
  96. console.log("Can't add listeners for object, which name is " + object.name + ": parser for STT " + object.stttype + " isn't defined");
  97. return;
  98. }
  99. this._patchMethod(object,"onkeyup");
  100. this._patchMethod(object,"onkeydown");
  101. this._patchMethod(object,"oncopy");
  102. this._patchMethod(object,"onpaste");
  103. this._patchMethod(object,"oncut");
  104. this._patchMethod(object,"onmousedown");
  105. }
  106. };
  107. BidiUtils.prototype._patchMethod = function(object,method){
  108. var oldMethod = object[method];
  109. var self = this;
  110. object[method] = function(event){
  111. self[method](object,event);
  112. if (oldMethod && typeof oldMethod === "function"){
  113. oldMethod.apply(object, arguments);
  114. }
  115. };
  116. };
  117. // The function is the bidi-specific handler for keyup event.
  118. // @object: element, whose onkeyup handler should be added or updated
  119. // @event: keyup event object
  120. BidiUtils.prototype.onkeyup = function(object,event){
  121. if (!object.stttype){
  122. object.dir = this.resolveStrBtd(object.value);
  123. }
  124. else{
  125. this._handleKey(object,event);
  126. }
  127. };
  128. // The function is the bidi-specific handler for keyup event.
  129. // @object: element, whose onkeyup handler should be added or updated
  130. // @event: keydown event object
  131. BidiUtils.prototype.onkeydown = function(object,event){
  132. if (!object.stttype){
  133. return;
  134. }
  135. this._processBackspaceDelete(object,event);
  136. };
  137. // The function is the bidi-specific handler for mousedown event.
  138. // @object: element, whose onmousedown handler should be added or updated
  139. // @event: event object
  140. BidiUtils.prototype.onmousedown = function(object,event){
  141. if (!object.stttype){
  142. return;
  143. }
  144. if (this.isIE){
  145. event = window.event;
  146. }
  147. this._adjustCaret(object,event,null);
  148. };
  149. // The function is the bidi-specific handler for copy event.
  150. // @object: element, whose oncopy handler should be added or updated
  151. // @event: event object
  152. BidiUtils.prototype.oncopy = function(object,event){
  153. if (!object.stttype){
  154. return;
  155. }
  156. this._processCopy(object,event);
  157. };
  158. // The function is the bidi-specific handler for paste event.
  159. // @object: element, whose onpaste handler should be added or updated
  160. // @event: event object
  161. BidiUtils.prototype.onpaste = function(object,event){
  162. if (!object.stttype){
  163. return;
  164. }
  165. this._processPaste(object,event);
  166. };
  167. // The function is the bidi-specific handler for cut event.
  168. // @object: element, whose oncut handler should be added or updated
  169. // @event: event object
  170. BidiUtils.prototype.oncut = function(object,event){
  171. if (!object.stttype){
  172. return;
  173. }
  174. this._processCut(object,event);
  175. };
  176. // The function is the bidi-specific handler for submit event.
  177. // It removes bidi markers (UCC) from values of the text fields,
  178. // which are placed in the given form and contain structured text.
  179. // @form: form containing text fields, values of which should be handled
  180. BidiUtils.prototype.onsubmit = function(form){
  181. var nType = ["input","textarea"];
  182. var nList;
  183. for (var i=0; i<2; i++){
  184. nList = form.getElementsByTagName(nType[i]);
  185. for (var i=0; i<nList.length; i++){
  186. var object = nList[i];
  187. if (object && object.attributes["stttype"]){
  188. object.value = this.removeUCCFromStr(object.value);
  189. }
  190. }
  191. }
  192. return true;
  193. };
  194. // The function is used to determine base direction of the given text.
  195. // @text: analyzed text
  196. BidiUtils.prototype.resolveStrBtd = function(text){
  197. var len = text.length;
  198. for(var i = 0;i < len;i++) {
  199. symbol = text.charCodeAt(i);
  200. if(this._isBidiChar(symbol))
  201. return "rtl";
  202. else if(this._isLatinChar(symbol))
  203. return "ltr";
  204. }
  205. return this.guiOrientation;
  206. };
  207. // The function injects UCC into the given string in accordance with the
  208. // given base text direction.
  209. // @text: text without UCC
  210. // @btd: base text direction
  211. BidiUtils.prototype.btdInjectUCCIntoStr = function(text, btd){
  212. if (typeof btd == "undefined"){
  213. btd = this.baseTextDirection;
  214. }
  215. if (btd == "auto"){
  216. btd = this.resolveStrBtd(text);
  217. }
  218. if (btd == "rtl"){
  219. return this.RLE + text + this.PDF;
  220. }
  221. else{
  222. return this.LRE + text + this.PDF;
  223. }
  224. };
  225. // The function sets dir attribute of the given element in accordance with the
  226. // given base text direction.
  227. // @objOrId: element or its id.
  228. // @btd: base text direction
  229. BidiUtils.prototype.btdSetDir = function(objOrDir, btd){
  230. var object = (typeof objOrDir == "string"? document.getElementById(objOrId) : objOrDir);
  231. if (!object){
  232. return;
  233. }
  234. if (typeof btd == "undefined"){
  235. btd = this.baseTextDirection;
  236. }
  237. if (btd == "auto"){
  238. btd = this.resolveStrBtd(object.value);
  239. }
  240. object.dir = btd;
  241. };
  242. //The function checks if the character is an Arabic character or not
  243. //@c: character to be tested.
  244. BidiUtils.prototype._isArabicChar = function(c){
  245. if (c >= 0x0600 && c <= 0x0669 ||
  246. c >= 0x06fa && c <= 0x07ff ||
  247. c >= 0xfb50 && c <= 0xfdff ||
  248. c >= 0xfe70 && c <= 0xfefc) {
  249. return true;
  250. }
  251. else{
  252. return false;
  253. }
  254. };
  255. //The function checks if the character is a Hebrew character or not
  256. //@c: character to be tested.
  257. BidiUtils.prototype._isHebrewChar = function(c){
  258. if (c >= 0x05d0 && c <= 0x05ff)
  259. return true;
  260. else
  261. return false;
  262. };
  263. BidiUtils.prototype._isLTRChar = function(c){
  264. if (c >= 0x0061 && c <= 0x007A ||
  265. c >= 0x0041 && c <= 0x005A ||
  266. c == 0x200E)
  267. return true;
  268. else
  269. return false;
  270. };
  271. // This function checks whether character been passed as parameter is Bidi character
  272. BidiUtils.prototype._isBidiChar = function(c){
  273. return (this._isArabicChar(c)||this._isHebrewChar(c));
  274. };
  275. // This function checks whether character been passed as parameter is Latin character
  276. BidiUtils.prototype._isLatinChar = function(c){
  277. if((c > 64 && c < 91)||(c > 96 && c < 123))
  278. return true;
  279. else
  280. return false;
  281. };
  282. // This function checks whether character been passed as parameter is digit
  283. BidiUtils.prototype._isDigit = function(c){
  284. if((c >= 48 && c <= 57)||(c >= 0x0660 && c <= 0x0669))
  285. return true;
  286. else
  287. return false;
  288. };
  289. // This function checks whether character found in str (first paramter) at position pos (second parameter)
  290. // and backwards [but not before position previousPos (third parameter)] is Bidi character.
  291. BidiUtils.prototype._isCharBeforeBiDiChar = function(str, pos, previousPos) {
  292. while (pos > 0){
  293. if(pos == previousPos){
  294. return false;
  295. }
  296. if(this._isBidiChar(str.charCodeAt(pos-1))){
  297. return true;
  298. }
  299. else if(this._isLatinChar(str.charCodeAt(pos-1))){
  300. return false;
  301. }
  302. pos--;
  303. }
  304. return false;
  305. };
  306. // This is auxiliary function called from keyup event callback.
  307. // @object: field where the typing takes place
  308. // @event: keydown event object
  309. BidiUtils.prototype._handleKey = function(object,event){
  310. if (!event) {
  311. event = window.event;
  312. }
  313. var code = event.keyCode;
  314. if (!code){
  315. code = event.which;
  316. }
  317. if ((code == this._K_HOME) || (code == this._K_END) || (code == this._K_SHIFT) ||
  318. (code == this._K_ENTER) || (code == this._K_INSERT) || (code == this._K_ESC)) {
  319. return;
  320. }
  321. else if ((code == this._K_UP) || (code == this._K_DOWN) ||
  322. (code == this._K_PAGEDOWN) || (code == this._K_PAGEUP)) {
  323. this._adjustCaret(object,event,null);
  324. return;
  325. }
  326. var str1 = object.value;
  327. var cursorStart, cursorEnd,cursorStart2,cursorEnd2;
  328. var selection = this._getCaretPos(object,event);
  329. var selection2;
  330. if (selection) {
  331. cursorStart = selection[0];
  332. cursorEnd = selection[1];
  333. }
  334. if(code == this._K_LEFT){
  335. if (this._checkMarkers(str1.charAt(cursorStart - 1)) && cursorStart == cursorEnd){
  336. this._setSelectedRange(object, cursorStart - 1, cursorEnd - 1);
  337. }
  338. return;
  339. }
  340. else if(code == this._K_RIGHT){
  341. if (this._checkMarkers(str1.charAt(cursorStart - 1)) && cursorStart == cursorEnd){
  342. cursorStart = (this.isIE) ? (cursorStart + 1) : cursorStart;
  343. this._setSelectedRange(object, cursorStart, cursorStart);
  344. }
  345. return;
  346. }
  347. var useArabicRulesForMultiEmail=this._useArabicRulesForMultiEmail(object, object.value);
  348. if (useArabicRulesForMultiEmail) {
  349. var markersCount = this._markersCount(str1, cursorStart);
  350. str2 = this.removeUCCFromStr(str1) ;
  351. this._setSelectedRange(object, cursorStart - markersCount, cursorEnd - markersCount);
  352. }
  353. else {
  354. str2 = this.removeUCCFromStr(str1);
  355. }
  356. if(useArabicRulesForMultiEmail) {
  357. str2=this._handle_ArabicEmail(str2,object);
  358. var selection2 = this._getCaretPos(object,event);
  359. if (selection2) {
  360. cursorStart2 = selection2[0];
  361. cursorEnd2 = selection2[1];
  362. }
  363. } else {
  364. str2 = this.sttInjectUCCIntoStr(str2,object.stttype);
  365. }
  366. if(str1 != str2){
  367. object.value = str2;
  368. if (code != this._K_DELETE && code != this._K_BACK){
  369. if(useArabicRulesForMultiEmail) {
  370. if (!this._checkUCC(object.value.charAt(cursorEnd))){
  371. this._setSelectedRange(object, cursorStart2+1 , cursorEnd2+1 );
  372. }
  373. }
  374. else {
  375. if (!this._checkMarkers(object.value.charAt(cursorEnd))){
  376. cursorStart += 1;
  377. cursorEnd += 1;
  378. }
  379. this._setSelectedRange(object, cursorStart, cursorEnd);
  380. }
  381. }
  382. else {
  383. if(useArabicRulesForMultiEmail) {
  384. this._setSelectedRange(object,cursorStart2,cursorEnd2);
  385. }
  386. else {
  387. this._setSelectedRange(object,cursorStart,cursorEnd);
  388. }
  389. }
  390. }
  391. };
  392. // This is auxiliary function called from keydown event callback.
  393. // Prevents Bidi markers from being erased on 'delete' and 'backspace' operations
  394. // @object: field where the typing takes place
  395. // @event: keydown event object
  396. BidiUtils.prototype._processBackspaceDelete = function(object,event){
  397. if (!event) {
  398. event = window.event;
  399. }
  400. var code = event.keyCode;
  401. if (!code){
  402. code = event.which;
  403. }
  404. if(code != this._K_DELETE && code != this._K_BACK) {
  405. return;
  406. }
  407. var cursorStart, cursorEnd;
  408. var selection = this._getCaretPos(object,event);
  409. if (selection) {
  410. cursorStart = selection[0];
  411. cursorEnd = selection[1];
  412. var str1,str2;
  413. if(cursorStart == cursorEnd) {
  414. if(this._useArabicRulesForMultiEmail(object, object.value)) {
  415. if ((code == this._K_DELETE) &&
  416. (this._checkUCC(object.value.charAt(cursorEnd))) &&
  417. (cursorEnd < object.value.length - 1)) {
  418. object.value = object.value.substring(0, cursorEnd) + object.value.substring(cursorEnd + 1);
  419. this._setSelectedRange(object, cursorEnd, cursorEnd);
  420. }
  421. else if ((code == this._K_BACK) && (this._checkUCC(object.value.charAt(cursorEnd - 1)))) {
  422. object.value = object.value.substring(0, cursorEnd - 1) + object.value.substring(cursorEnd);
  423. this._setSelectedRange(object, cursorEnd - 1, cursorEnd - 1);
  424. }
  425. }
  426. else {
  427. if ((code == this._K_DELETE) &&
  428. (this._checkMarkers(object.value.charAt(cursorEnd))) && (cursorEnd < object.value.length - 1)) {
  429. object.value = object.value.substring(0, cursorEnd) + object.value.substring(cursorEnd + 1);
  430. this._setSelectedRange(object, cursorEnd, cursorEnd);
  431. }
  432. else if ((code == this._K_BACK) && (this._checkMarkers(object.value.charAt(cursorEnd - 1)))) {
  433. object.value = object.value.substring(0, cursorEnd - 1) + object.value.substring(cursorEnd);
  434. this._setSelectedRange(object, cursorEnd - 1, cursorEnd - 1);
  435. }
  436. }
  437. }
  438. }
  439. };
  440. // This is auxiliary function called from different event callbacks.
  441. // Ensures that caret placement doesn't lead to erasing Bidi marker.
  442. // @object: field where the operation takes place
  443. // @event: event object
  444. // @caretPosition: current caret position
  445. BidiUtils.prototype._adjustCaret = function(object,event,caretPosition){
  446. if(caretPosition || (event.button != 2)){
  447. var selection = (caretPosition) ? caretPosition : this._getCaretPos(object,event);
  448. if (selection){
  449. if (selection[0] == selection[1]) {
  450. if(this._checkMarkers(object.value.charAt(selection[1] - 1))) {
  451. selection[1] += 1;
  452. selection[0] = null;
  453. }
  454. if(caretPosition || !selection[0]) {
  455. this._setSelectedRange(object, selection[1], selection[1]);
  456. }
  457. }
  458. }
  459. }
  460. };
  461. // This is auxiliary function called from 'copy' event callback
  462. // in order to ensure correct STT processing
  463. // @object: field where the operation takes place
  464. // @event: event object
  465. BidiUtils.prototype._processCopy = function(object,event){
  466. var text = "";
  467. try{
  468. if (this.isIE) {
  469. var w = object.document.parentWindow;
  470. event = w.event;
  471. var range = object.document.selection.createRange();
  472. text = range.text;
  473. }
  474. else {
  475. text = object.value.substring(object.selectionStart,object.selectionEnd);
  476. }
  477. var textToClipboard = this.removeUCCFromStr(text);
  478. if (window.clipboardData) {
  479. window.clipboardData.setData("Text", textToClipboard);
  480. if (this.isIE || event.returnValue){
  481. event.returnValue = false;
  482. }
  483. else if(event.preventDefault){
  484. event.preventDefault();
  485. }
  486. }
  487. }catch(ex){console.log("Exception in copy");}
  488. };
  489. // This is auxiliary function called from 'paste' event callback
  490. // in order to ensure correct STT processing
  491. // @object: field where the operation takes place
  492. // @event: event object
  493. BidiUtils.prototype._processPaste = function(object, event){
  494. if(this.isIE) {
  495. try {
  496. var w = object.document.parentWindow;
  497. event = w.event;
  498. this.caretPos = this._getCaretPos(object,event);
  499. var range = document.selection.createRange();
  500. var sClipboardData = window.clipboardData.getData("Text");
  501. range.text = sClipboardData;
  502. this.caretPos[0] = this.caretPos[1] = this.caretPos[0] +
  503. this.sttInjectUCCIntoStr(sClipboardData,object.stttype).length;
  504. this.removeUCCFromObj(object);
  505. this.sttInjectUCCIntoObj(object);
  506. var thisObj = this;
  507. setTimeout(function() {
  508. thisObj._adjustCaret(object,event,thisObj.caretPos);
  509. }, 100);
  510. event.returnValue = false;
  511. }catch(ex){console.log("Exception in paste");}
  512. }
  513. };
  514. // This is auxiliary function called from 'cut' event callback
  515. // in order to ensure correct STT processing
  516. // @object: field where the operation takes place
  517. // @event: event object
  518. BidiUtils.prototype._processCut = function(object,event){
  519. this._processCopy(object,event);
  520. this.caretPos = this._getCaretPos(object,event);
  521. if(this.isIE) {
  522. document.selection.clear();
  523. }
  524. else{
  525. object.value = object.value.substring(0,object.selectionStart) + object.value.substring(object.selectionEnd);
  526. }
  527. this.caretPos[0] = this.caretPos[1] = Math.min(this.caretPos[0],this.caretPos[1]);
  528. this.removeUCCFromObj(object);
  529. this.sttInjectUCCIntoObj(object);
  530. this._adjustCaret(object,event,this.caretPos);
  531. };
  532. // This is auxiliary function called from different event callbacks
  533. // Returns current caret position
  534. // @object: field where the typing takes place
  535. // @event: event object
  536. BidiUtils.prototype._getCaretPos = function(object,event){
  537. if (!this.isIE)
  538. return new Array(object.selectionStart, object.selectionEnd);
  539. else {
  540. var range = document.selection.createRange().duplicate();
  541. var rangeLength = range.text.length;
  542. range.moveStart('character', -object.value.length);
  543. return new Array(range.text.length - rangeLength, range.text.length);
  544. }
  545. };
  546. // This is auxiliary function called from different event callbacks
  547. // Sets the selection (caret position) at selectionStart-selectionEnd
  548. BidiUtils.prototype._setSelectedRange = function(object,selectionStart,selectionEnd){
  549. if(this.isIE) {
  550. var range = object.createTextRange();
  551. if (range) {
  552. if (object.type == "textarea"){
  553. range.moveToElementText(object);
  554. }
  555. else{
  556. range.expand('textedit');
  557. }
  558. range.collapse();
  559. range.moveEnd('character', selectionEnd);
  560. range.moveStart('character', selectionStart);
  561. range.select();
  562. }
  563. }
  564. else {
  565. object.selectionStart=selectionStart;
  566. object.selectionEnd=selectionEnd;
  567. }
  568. };
  569. // This function checks whether character been passed as parameter is LRM/RLM
  570. BidiUtils.prototype._checkMarkers = function(ch) {
  571. return (ch == this.LRM || ch == this.RLM);
  572. };
  573. //The function checks if the character at position @pos is a Unicode Control Character
  574. //@str: String to be tested.
  575. //@pos: position of character to be checked.
  576. BidiUtils.prototype._checkUCC = function(str,pos) {
  577. if (pos >= str.length){
  578. return false;
  579. }
  580. if(pos < 0) {
  581. return false;
  582. }
  583. var c = str.charAt(pos);
  584. if (this._isUCChar(c)) {
  585. return true;
  586. }
  587. return false;
  588. };
  589. //The function checks if the character is a Unicode Control Character
  590. // @c: character to be tested.
  591. BidiUtils.prototype._isUCChar = function(c) {
  592. if (c == this.LRM || c == this.RLM || c == this.LRE || c == this.RLE || c == this.PDF || c == this.ZWNJ){
  593. return true;
  594. }
  595. return false;
  596. };
  597. // The function removes bidi markers (UCC) from the string,
  598. // containing structured text.
  599. // @str: structured text, containing UCC
  600. BidiUtils.prototype.removeUCCFromStr = function(str){
  601. if (!str) {
  602. return str;
  603. }
  604. return str.replace(/\u200E|\u200F|\u200F|\u202A|\u202B|\u200C|\u202C/g,"")
  605. };
  606. //The function removes bidi markers (UCC) from the value
  607. //of the object, containing structured text.
  608. // @object: object, containing structured text with UCC
  609. BidiUtils.prototype.removeUCCFromObj = function(object) {
  610. if (object && object.value){
  611. object.value = this.removeUCCFromStr(object.value);
  612. }
  613. };
  614. // The function injects UCC into given string in accordance with the
  615. // given type of structured text.
  616. // @str: structured text without UCC
  617. // @sttType: type of the structure
  618. BidiUtils.prototype.sttInjectUCCIntoStr = function(str, sttType){
  619. var useArabicRulesForMultiEmail = false;
  620. if (sttType == this.multiemail) {
  621. if(this._isArabicText(str)) {
  622. useArabicRulesForMultiEmail = true;
  623. }
  624. }
  625. if (sttType == "multiemail" && useArabicRulesForMultiEmail) {
  626. return this._handle_ArabicEmail_Static(str);
  627. }
  628. else if(sttType == 'crumb') {
  629. return this._processBreadCrumb(str);
  630. }
  631. else {
  632. var txt = str.replace(/\\/g,"\\\\");
  633. txt = txt.replace(/'/g,"\\'");
  634. var buf = str;
  635. var sPoints = this["_parse_" + sttType](txt);
  636. if (typeof sPoints == "undefined"){
  637. console.log("Parser for STT '" + sttType + "' isn't correct");
  638. return str;
  639. }
  640. var shift = 0;
  641. var n, preStr, postStr;
  642. for (var i = 0; i< sPoints.length; i++) {
  643. n = sPoints[i];
  644. if(n != null){
  645. preStr = buf.substring(0, n + shift);
  646. postStr = buf.substring(n + shift, buf.length);
  647. buf = preStr + this.LRM + postStr;
  648. shift++;
  649. }
  650. }
  651. return buf;
  652. }
  653. };
  654. // The function injects UCC into value of the given object in accordance
  655. // with its type of structured text.
  656. // @object: object, containig structured text without UCC
  657. BidiUtils.prototype.sttInjectUCCIntoObj = function(object){
  658. if (!object.stttype){
  659. if (object.attributes["stttype"]){
  660. object.stttype = object.attributes["stttype"].nodeValue;
  661. }
  662. else{
  663. return;
  664. }
  665. }
  666. object.value = this.sttInjectUCCIntoStr(object.value, object.stttype);
  667. };
  668. // This is auxiliary function called from _processBreadCrumb
  669. // Inserts Bidi marks in proper places of bread crumb segment
  670. BidiUtils.prototype._processSegment = function(sText) {
  671. var sDir = this.baseTextDirection;
  672. if(sDir == 'auto') {
  673. sDir = (this.resolveStrBtd(sText) ? 'rtl' : 'ltr');
  674. }
  675. var directionMark = this.BACKREF;
  676. directionMark += (sDir == 'rtl') ? this.RLE : this.LRE;
  677. sText = sText.replace(/(^\s{0,})/,directionMark);
  678. return sText.replace(/(\s{0,}$)/,this.BACKREF_PDF);
  679. };
  680. /**
  681. * Inserts unicode Bidi directional marks before (LRE/RLE) and after (PDF) every
  682. * subsegment and fixes the delimiter positions by strong directional mark LRM
  683. *
  684. * @param {string} sText, containing complex expression to be processed
  685. * @return {string} processed input string
  686. */
  687. BidiUtils.prototype._processBreadCrumb = function(sText) {
  688. var sBuff = "";
  689. var strongSeparator = (this.guiOrientation == 'ltr') ? this.LRM : this.RLM;
  690. var splitArray = sText.split(':');
  691. if(splitArray.length > 1) {
  692. sText = splitArray[1];
  693. sBuff = splitArray[0] + ':' + strongSeparator;
  694. }
  695. var splitArray = sText.split('>');
  696. for (var i = 0; i < splitArray.length; i++) {
  697. splitArray[i] = this._processSegment(splitArray[i]);
  698. }
  699. sBuff += splitArray.join('>' + strongSeparator);
  700. return sBuff;
  701. };
  702. /**
  703. * Parses string containing complex expression.
  704. *
  705. * @param {string} str, input string to be parsed
  706. * @param {string} delimiters,set of delimiters dedining the type of complex expression
  707. * @return {Array} array containing points in input string where UCC marks should be inserted
  708. */
  709. BidiUtils.prototype._parse_complex_string = function(str, delimiters){
  710. var sPoints = new Array();
  711. var previous = -1;
  712. for (var i = 0; i < str.length; i++){
  713. if ((delimiters.indexOf(str.charAt(i)) >= 0) && this._isCharBeforeBiDiChar(str,i,previous)){
  714. previous = i;
  715. sPoints.push(i);
  716. }
  717. }
  718. return sPoints;
  719. };
  720. /**
  721. * Parses string containing filepath complex expression.
  722. *
  723. * @param {string} str, input string to be parsed
  724. */
  725. BidiUtils.prototype._parse_filepath = function(str){
  726. var delimiters = "/\\.";
  727. return this._parse_complex_string(str,delimiters);
  728. };
  729. /**
  730. * Parses string containing url complex expression.
  731. *
  732. * @param {string} str, input string to be parsed
  733. */
  734. BidiUtils.prototype._parse_url = function(str){
  735. var delimiters = "/.?:&=;";
  736. return this._parse_complex_string(str,delimiters);
  737. };
  738. /**
  739. * Parses string containing multiemail complex expression.
  740. *
  741. * @param {string} str, input string to be parsed
  742. */
  743. BidiUtils.prototype._parse_multiemail = function(str){
  744. var delimiters = "<>@.,;";
  745. return this._parse_complex_string(str,delimiters);
  746. };
  747. /**
  748. * Parses string containing email complex expression.
  749. *
  750. * @param {string} str, input string to be parsed
  751. */
  752. BidiUtils.prototype._parse_email = function(str){
  753. var delimiters = "<>@.,;";
  754. var sPoints = new Array();
  755. var previous = -1, j;
  756. for (var i = 0; i < str.length; i++){
  757. if (str.charAt(i) == '\"') {
  758. if (this._isCharBeforeBiDiChar(str,i,previous)){
  759. previous = i;
  760. sPoints.push(i);
  761. }
  762. i++;
  763. j = str.indexOf('\"', i);
  764. if(j >= i){
  765. i = j;
  766. }
  767. else{
  768. continue;
  769. }
  770. if (this._isCharBeforeBiDiChar(str,i,previous)){
  771. previous = i;
  772. sPoints.push(i);
  773. }
  774. }
  775. if ((delimiters.indexOf(str.charAt(i)) >= 0) && this._isCharBeforeBiDiChar(str,i,previous)){
  776. previous = i;
  777. sPoints.push(i);
  778. }
  779. }
  780. return sPoints;
  781. };
  782. //The function counts the Unicode Control Characters before character at position @len
  783. //@str: String to be tested.
  784. //@len: number of characters to count the markers in them.
  785. BidiUtils.prototype._markersCount = function(str, len){
  786. var chr;
  787. var markersCount = 0;
  788. var i;
  789. if (str.length < 1)
  790. return 0;
  791. for( i = 0; i < len - 1; i++) {
  792. chr = str.charAt(i);
  793. if (this._isUCChar(chr))
  794. markersCount += 1;
  795. }
  796. return markersCount;
  797. };
  798. //The function inserts Unicode Control characters into string containing
  799. //multiple arabic emails and modify cursor position to keep the text well
  800. //formated during editing
  801. //@sttString: String containing Email to be handled.
  802. //@object: Object holding cursor info to be handled.
  803. BidiUtils.prototype._handle_ArabicEmail = function(sttString, object){
  804. var resultStr = "";
  805. var chr;
  806. var lastSeparatorIndex = -1, lastDelimiterIndex = -1;
  807. var isLastRTL = false, isLastLTR = false;
  808. var currentPosition = -1, newPosition = -1;
  809. var markersCount = 0;
  810. var i, j;
  811. var selectionStart;
  812. var mailDelimiters = "@.<>";
  813. var mailSeparators = ",;";
  814. var mailSign = "@";
  815. var prevEmailDir = 0;
  816. var cEmbeding, cMarker, cEmailDir = 0;
  817. var mailSignAvailable = false;
  818. var isAdded = false;
  819. var selection = this._getCaretPos(object, null);
  820. if (selection) {
  821. var selectionStart = selection[0];
  822. var selEnd = selection[1];
  823. }
  824. currentPosition = selectionStart;
  825. newPosition = selectionStart;
  826. markersCount = this._markersCount(sttString, currentPosition);
  827. sttString = this.removeUCCFromStr(sttString);
  828. newPosition = newPosition - markersCount;
  829. if (sttString.length > 0 && mailDelimiters.length > 0) {
  830. cMarker = this.LRM;
  831. cEmbeding = this.LRE;
  832. cEmailDir = this.LTR_DIR;
  833. resultStr = cEmbeding + resultStr;
  834. for (i = 0; i < sttString.length; i++) {
  835. chrCode = sttString.charCodeAt(i);
  836. chr = sttString.charAt(i);
  837. if (mailDelimiters.indexOf(chr) != -1) {
  838. if (mailSign.indexOf(chr) != -1) {
  839. mailSignAvailable = true;
  840. }
  841. if ((cEmailDir == this.LTR_DIR && isLastRTL) || (cEmailDir == this.RTL_DIR && isLastLTR)) {
  842. lastDelimiterIndex = resultStr.length;
  843. }
  844. }
  845. else if (mailSeparators.indexOf(chr) != -1) {
  846. mailSignAvailable = false;
  847. lastSeparatorIndex = i;
  848. resultStr = resultStr + this.PDF;
  849. lastDelimiterIndex = resultStr.length;
  850. }
  851. else if (this._isDigit(chrCode)) {
  852. if (lastDelimiterIndex != -1) {
  853. resultStr = resultStr.substring(0, lastDelimiterIndex) + cMarker + resultStr.substring(lastDelimiterIndex);
  854. lastDelimiterIndex = -1;
  855. isLastRTL = false;
  856. isLastLTR = false;
  857. }
  858. }
  859. else if (this._isBidiChar(chrCode) || this._isLTRChar(chrCode)) {
  860. if (mailSignAvailable) {
  861. var nextSeparatorIndex = this._nextSeparator(sttString, i);
  862. var mailSegment = sttString.substring(lastSeparatorIndex + 1, nextSeparatorIndex + 1);
  863. var cEmailDir = this._getArabicEmailDirection(mailSegment);
  864. if ( cEmailDir == this.RTL_DIR ) {
  865. cMarker = this.RLM;
  866. cEmbeding = this.RLE;
  867. }
  868. else {
  869. cMarker = this.LRM;
  870. cEmbeding = this.LRE;
  871. }
  872. if (prevEmailDir != cEmailDir && cEmailDir != 0) {
  873. var lastMailSeparator = this._lastMailSeparator(resultStr, resultStr.length);
  874. var tmpStr = this.removeUCCFromStr(sttString.substring(lastSeparatorIndex + 1, i + 1));
  875. var mailSTT = this._handle_ArabicSingleEmail(tmpStr, cEmailDir);
  876. if (lastMailSeparator > 0)
  877. resultStr = resultStr.substring(0, lastMailSeparator + 1) + mailSTT.substring(0, mailSTT.length - 1);
  878. else
  879. resultStr = mailSTT.substring(0, mailSTT.length - 1);
  880. isAdded = true;
  881. }
  882. prevEmailDir = cEmailDir;
  883. }
  884. if (this._isBidiChar(chrCode)) {
  885. if ((cEmailDir == this.LTR_DIR) && lastDelimiterIndex != -1) {
  886. resultStr = resultStr.substring(0, lastDelimiterIndex) + this.LRM + resultStr.substring(lastDelimiterIndex);
  887. lastDelimiterIndex = -1;
  888. }
  889. isLastRTL = true;
  890. isLastLTR = false;
  891. }
  892. else {
  893. if ((cEmailDir == this.RTL_DIR) && lastDelimiterIndex != -1) {
  894. resultStr = resultStr.substring(0, lastDelimiterIndex) + this.RLM + resultStr.substring(lastDelimiterIndex);
  895. lastDelimiterIndex = -1;
  896. }
  897. isLastLTR = true;
  898. isLastRTL = false;
  899. }
  900. }
  901. if (isAdded) {
  902. isAdded = false;
  903. } else {
  904. resultStr = resultStr + chr;
  905. }
  906. if (mailSeparators.indexOf(chr) != -1) {
  907. resultStr = resultStr + cEmbeding;
  908. }
  909. }
  910. resultStr = resultStr + this.PDF;
  911. }
  912. var newPosition=0;
  913. var j = currentPosition;
  914. while(newPosition < j && newPosition < resultStr.length) {
  915. if (this._isUCChar(resultStr.charAt(newPosition))) {
  916. j++;
  917. }
  918. newPosition++;
  919. }
  920. object.value=resultStr;
  921. this._setSelectedRange(object, newPosition, newPosition);
  922. return resultStr;
  923. };
  924. //The function inserts markers into single Arabic email
  925. //@sttString: String containing email.
  926. //@dir: base text direction of the email.
  927. BidiUtils.prototype._handle_ArabicSingleEmail = function(sttString, dir){
  928. var resultStr = "";
  929. var chr;
  930. var lastDelimiterIndex = -1;
  931. var isLastRTL = false, isLastLTR = false;
  932. var cEmbeding, cMarker;
  933. var i, j;
  934. var mailDelimiters = "@.<>";
  935. var cEmailDir = 0;
  936. cEmailDir = dir;
  937. if ( cEmailDir == this.RTL_DIR ) {
  938. cMarker = this.RLM;
  939. cEmbeding = this.RLE;
  940. }
  941. else {
  942. cMarker = this.LRM;
  943. cEmbeding = this.LRE;
  944. }
  945. if (sttString.length > 0 && mailDelimiters.length > 0) {
  946. for (i = 0; i < sttString.length; i++) {
  947. chrCode = sttString.charCodeAt(i);
  948. chr = sttString.charAt(i);
  949. if (mailDelimiters.indexOf(chr) != -1) {
  950. if ((cEmailDir == this.LTR_DIR && isLastRTL) || (cEmailDir == this.RTL_DIR && isLastLTR)) {
  951. lastDelimiterIndex = resultStr.length;
  952. }
  953. }
  954. else if (this._isDigit(chrCode)) {
  955. if (lastDelimiterIndex != -1) {
  956. resultStr = resultStr.substring(0, lastDelimiterIndex) + cMarker + resultStr.substring(lastDelimiterIndex);
  957. lastDelimiterIndex = -1;
  958. isLastRTL = false;
  959. isLastLTR = false;
  960. }
  961. }
  962. else if (this._isBidiChar(chrCode) || this._isLTRChar(chrCode)) {
  963. if (this._isBidiChar(chrCode)) {
  964. if ((cEmailDir == this.LTR_DIR) && lastDelimiterIndex != -1) {
  965. resultStr = resultStr.substring(0, lastDelimiterIndex) + this.LRM + resultStr.substring(lastDelimiterIndex);
  966. lastDelimiterIndex = -1;
  967. }
  968. isLastRTL = true;
  969. isLastLTR = false;
  970. }
  971. else {
  972. if ((cEmailDir == this.RTL_DIR) && lastDelimiterIndex != -1) {
  973. resultStr = resultStr.substring(0, lastDelimiterIndex) + this.RLM + resultStr.substring(lastDelimiterIndex);
  974. lastDelimiterIndex = -1;
  975. }
  976. isLastLTR = true;
  977. isLastRTL = false;
  978. }
  979. }
  980. resultStr = resultStr + chr;
  981. }
  982. resultStr = cEmbeding + resultStr + this.PDF;
  983. }
  984. return resultStr;
  985. };
  986. //The function inserts markers into static emails containing Arabic characters
  987. //@str: String containing email addresses.
  988. BidiUtils.prototype._handle_ArabicEmail_Static = function(str) {
  989. if (str != null && str.length > 0) {
  990. var cPosition = 0;
  991. var result = "";
  992. var separators=";,";
  993. var lastCharIsSeparator=false;
  994. while (cPosition < str.length) {
  995. var nextSeparatorIndex = this._nextSeparator(str, cPosition);
  996. var mail = str.substring(cPosition, nextSeparatorIndex);
  997. var dir = this._getArabicEmailDirection(mail);
  998. if (nextSeparatorIndex < str.length-1) {
  999. result = result + this._handle_ArabicSingleEmail(mail, dir) + str.charAt(nextSeparatorIndex) ;
  1000. }
  1001. else {
  1002. if(separators.indexOf(str.charAt(nextSeparatorIndex))>-1) {
  1003. lastCharIsSeparator=true;
  1004. }
  1005. else {
  1006. lastCharIsSeparator=false;
  1007. }
  1008. if(lastCharIsSeparator) {
  1009. result = result + this._handle_ArabicSingleEmail(mail, dir)+ str.charAt(nextSeparatorIndex) ;
  1010. }
  1011. else {
  1012. mail=mail+str.charAt(nextSeparatorIndex);
  1013. result = result + this._handle_ArabicSingleEmail(mail, dir) ;
  1014. }
  1015. }
  1016. cPosition = nextSeparatorIndex + 1;
  1017. }
  1018. return result;
  1019. }
  1020. return str;
  1021. };
  1022. //The function determines the position of the last email separator in the string
  1023. //@str: String to be tested.
  1024. //@pos: current position (to start searching from the character after it)
  1025. BidiUtils.prototype._lastMailSeparator = function(str, start){
  1026. var lastComma = str.lastIndexOf(',', start);
  1027. var lastSemiColon = str.lastIndexOf(';', start);
  1028. return Math.max(lastComma, lastSemiColon, 0);
  1029. };
  1030. //The function determines the position of the next email separator in the string, starting from the current position
  1031. //@str: String to be tested.
  1032. //@pos: current position (to start searching from the character after it)
  1033. BidiUtils.prototype._nextSeparator = function(str,start){
  1034. var nextComma=str.indexOf(',',start);
  1035. var len = str.length;
  1036. var nextSemiColon=str.indexOf(';',start);
  1037. if(nextComma<0) {
  1038. nextComma=len-1;
  1039. }
  1040. if(nextSemiColon<0) {
  1041. nextSemiColon=len-1;
  1042. }
  1043. return Math.min(nextComma,nextSemiColon);
  1044. };
  1045. //The function checks if the string should be considered Arabic or not
  1046. //The string is considered Arabic if the first Bidi character found was Arabic
  1047. //@str: string to be tested.
  1048. BidiUtils.prototype._isArabicText = function(str){
  1049. if(!str) {
  1050. return false;
  1051. }
  1052. var len=str.length;
  1053. if(len<1) return false;
  1054. for(var i=0;i<len;i++) {
  1055. //If an Arabic Char is found first, the text is considered Arabic
  1056. if(this._isArabicChar(str.charCodeAt(i))) {
  1057. return true;
  1058. }
  1059. else {
  1060. if(this._isHebrewChar(str.charCodeAt(i))) {
  1061. return false;
  1062. }
  1063. }
  1064. }
  1065. return false;
  1066. };
  1067. //The function checks if the String contains Bidi characters or not
  1068. //@str: String to be tested.
  1069. BidiUtils.prototype._isBidiText = function(str){
  1070. if(str==null) return false;
  1071. var len=str.length;
  1072. if(len<1) return false;
  1073. for(var i=0;i<len;i++) {
  1074. //If an Arabic Char is found first, the text is considered Hebrew
  1075. if(this._isArabicChar(str.charCodeAt(i))||this._isHebrewChar(str.charCodeAt(i))) {
  1076. return true;
  1077. }
  1078. }
  1079. return false;
  1080. };
  1081. //The function checks if the string should be considered Hebrew or not
  1082. //The string is considered Hebrew if the first Bidi character found was Hebrew
  1083. //@str: string to be tested.
  1084. BidiUtils.prototype._isHebrewText = function(str){
  1085. if(!str) {
  1086. return false;
  1087. }
  1088. var len=str.length;
  1089. if(len<1) return false;
  1090. for(var i=0;i<len;i++) {
  1091. //If a Hebrew Char is found first, the text is considered Hebrew
  1092. if(this._isHebrewChar(str.charCodeAt(i))) {
  1093. return true;
  1094. }
  1095. else {
  1096. if(this._isArabicChar(str.charCodeAt(i))) {
  1097. return false;
  1098. }
  1099. }
  1100. }
  1101. return false;
  1102. };
  1103. //The function checks if the string may be an email address or not
  1104. //@str: String to be tested.
  1105. BidiUtils.prototype._isEmailAddress = function(str){
  1106. if(str==null) return false;
  1107. if(str.length<1) return false;
  1108. if(str.indexOf('@')<0) {
  1109. return false;
  1110. }
  1111. return true;
  1112. };
  1113. //The function returns the domain of the email contained in the string @str
  1114. //@str: String representing the full email address .
  1115. BidiUtils.prototype._getEmailDomain = function(str){
  1116. var segments=str.split('@');
  1117. return segments[1];
  1118. };
  1119. //The function decides if the text direction of email is LTR or RTL
  1120. //If the domain contains Bidi characters, the text direction is RTL
  1121. //else the text direction is LTR
  1122. //@str: String representing email address to be tested.
  1123. BidiUtils.prototype._getArabicEmailDirection = function(str){
  1124. var domain = this._getEmailDomain(str);
  1125. if(this._isBidiText(domain)) {
  1126. return this.RTL_DIR;
  1127. }
  1128. else {
  1129. return this.LTR_DIR;
  1130. }
  1131. };
  1132. //The function determines if Arabic rules should be used for emails as STT
  1133. //Should return true if the stttype of the object is multiemail and the text is considered Arabic
  1134. //if the passed String is used and if it is equal to null the value in the object is used instead
  1135. //@object: Object whose value will be processed.
  1136. //@str: The string to be tested if it is Arabic or not
  1137. BidiUtils.prototype._useArabicRulesForMultiEmail = function(object,str){
  1138. if(!object) {
  1139. return false;
  1140. }
  1141. if(!str) {
  1142. str=object.value;
  1143. if(!str) {
  1144. return false;
  1145. }
  1146. }
  1147. if((object.stttype==this.multiemail) || (object.stttype=="email")) {
  1148. if(this._isArabicText(str)) {
  1149. return true;
  1150. }
  1151. }
  1152. return false;
  1153. };