1/** @license Hyphenator 3.1.0 - client side hyphenation for webbrowsers 2 * Copyright (C) 2010 Mathias Nater, Zürich (mathias at mnn dot ch) 3 * Project and Source hosted on http://code.google.com/p/hyphenator/ 4 * 5 * This JavaScript code is free software: you can redistribute 6 * it and/or modify it under the terms of the GNU Lesser 7 * General Public License (GNU LGPL) as published by the Free Software 8 * Foundation, either version 3 of the License, or (at your option) 9 * any later version. The code is distributed WITHOUT ANY WARRANTY; 10 * without even the implied warranty of MERCHANTABILITY or FITNESS 11 * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. 12 * 13 * As additional permission under GNU GPL version 3 section 7, you 14 * may distribute non-source (e.g., minimized or compacted) forms of 15 * that code without the copy of the GNU GPL normally required by 16 * section 4, provided you include this license notice and a URL 17 * through which recipients can access the Corresponding Source. 18 */ 19 20/* 21 * Comments are jsdoctoolkit formatted. See http://code.google.com/p/jsdoc-toolkit/ 22 */ 23 24/* The following comment is for JSLint: */ 25/*global window, ActiveXObject, unescape */ 26/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, regexp: true, sub: true, newcap: true, immed: true, evil: true, eqeqeq: false */ 27 28 29/** 30 * @constructor 31 * @description Provides all functionality to do hyphenation, except the patterns that are loaded 32 * externally. 33 * @author Mathias Nater, <a href = "mailto:mathias@mnn.ch">mathias@mnn.ch</a> 34 * @version 3.1.0 35 * @namespace Holds all methods and properties 36 * @example 37 * <script src = "Hyphenator.js" type = "text/javascript"></script> 38 * <script type = "text/javascript"> 39 * Hyphenator.run(); 40 * </script> 41 */ 42var Hyphenator = (function (window) { 43 44 var 45 /** 46 * @name Hyphenator-supportedLang 47 * @description 48 * A key-value object that stores supported languages. 49 * The key is the bcp47 code of the language and the value 50 * is the (abbreviated) filename of the pattern file. 51 * @type {Object.<string, string>} 52 * @private 53 * @example 54 * Check if language lang is supported: 55 * if (supportedLang.hasOwnProperty(lang)) 56 */ 57 supportedLang = { 58 'be': 'be.js', 59 'cs': 'cs.js', 60 'da': 'da.js', 61 'bn': 'bn.js', 62 'de': 'de.js', 63 'el': 'el-monoton.js', 64 'el-monoton': 'el-monoton.js', 65 'el-polyton': 'el-polyton.js', 66 'en': 'en-us.js', 67 'en-gb': 'en-gb.js', 68 'en-us': 'en-us.js', 69 'es': 'es.js', 70 'fi': 'fi.js', 71 'fr': 'fr.js', 72 'grc': 'grc.js', 73 'gu': 'gu.js', 74 'hi': 'hi.js', 75 'hu': 'hu.js', 76 'hy': 'hy.js', 77 'it': 'it.js', 78 'kn': 'kn.js', 79 'la': 'la.js', 80 'lt': 'lt.js', 81 'ml': 'ml.js', 82 'nl': 'nl.js', 83 'or': 'or.js', 84 'pa': 'pa.js', 85 'pl': 'pl.js', 86 'pt': 'pt.js', 87 'ru': 'ru.js', 88 'sl': 'sl.js', 89 'sv': 'sv.js', 90 'ta': 'ta.js', 91 'te': 'te.js', 92 'tr': 'tr.js', 93 'uk': 'uk.js' 94 }, 95 96 /** 97 * @name Hyphenator-languageHint 98 * @description 99 * An automatically generated string to be displayed in a prompt if the language can't be guessed. 100 * The string is generated using the supportedLang-object. 101 * @see Hyphenator-supportedLang 102 * @type {string} 103 * @private 104 * @see Hyphenator-autoSetMainLanguage 105 */ 106 107 languageHint = (function () { 108 var k, r = ''; 109 for (k in supportedLang) { 110 if (supportedLang.hasOwnProperty(k)) { 111 r += k + ', '; 112 } 113 } 114 r = r.substring(0, r.length - 2); 115 return r; 116 }()), 117 118 /** 119 * @name Hyphenator-prompterStrings 120 * @description 121 * A key-value object holding the strings to be displayed if the language can't be guessed 122 * If you add hyphenation patterns change this string. 123 * @type {Object.<string,string>} 124 * @private 125 * @see Hyphenator-autoSetMainLanguage 126 */ 127 prompterStrings = { 128 'be': 'Мова гэтага сайта не можа быць вызначаны аўтаматычна. Калі ласка пакажыце мову:', 129 'cs': 'Jazyk této internetové stránky nebyl automaticky rozpoznán. Určete prosím její jazyk:', 130 'da': 'Denne websides sprog kunne ikke bestemmes. Angiv venligst sprog:', 131 'de': 'Die Sprache dieser Webseite konnte nicht automatisch bestimmt werden. Bitte Sprache angeben:', 132 'en': 'The language of this website could not be determined automatically. Please indicate the main language:', 133 'es': 'El idioma del sitio no pudo determinarse autom%E1ticamente. Por favor, indique el idioma principal:', 134 'fi': 'Sivun kielt%E4 ei tunnistettu automaattisesti. M%E4%E4rit%E4 sivun p%E4%E4kieli:', 135 'fr': 'La langue de ce site n%u2019a pas pu %EAtre d%E9termin%E9e automatiquement. Veuillez indiquer une langue, s.v.p.%A0:', 136 'hu': 'A weboldal nyelvét nem sikerült automatikusan megállapítani. Kérem adja meg a nyelvet:', 137 'hy': 'Չհաջողվեց հայտնաբերել այս կայքի լեզուն։ Խնդրում ենք նշեք հիմնական լեզուն՝', 138 'it': 'Lingua del sito sconosciuta. Indicare una lingua, per favore:', 139 'kn': 'ಜಾಲ ತಾಣದ ಭಾಷೆಯನ್ನು ನಿರ್ಧರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಮುಖ್ಯ ಭಾಷೆಯನ್ನು ಸೂಚಿಸಿ:', 140 'lt': 'Nepavyko automatiškai nustatyti šios svetainės kalbos. Prašome įvesti kalbą:', 141 'ml': 'ഈ വെ%u0D2C%u0D4D%u200Cസൈറ്റിന്റെ ഭാഷ കണ്ടുപിടിയ്ക്കാ%u0D28%u0D4D%u200D കഴിഞ്ഞില്ല. ഭാഷ ഏതാണെന്നു തിരഞ്ഞെടുക്കുക:', 142 'nl': 'De taal van deze website kan niet automatisch worden bepaald. Geef de hoofdtaal op:', 143 'pt': 'A língua deste site não pôde ser determinada automaticamente. Por favor indique a língua principal:', 144 'ru': 'Язык этого сайта не может быть определен автоматически. Пожалуйста укажите язык:', 145 'sl': 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:', 146 'sv': 'Spr%E5ket p%E5 den h%E4r webbplatsen kunde inte avg%F6ras automatiskt. V%E4nligen ange:', 147 'tr': 'Bu web sitesinin dilini otomatik olarak tespit edilememiştir. Lütfen ana dili gösterir:', 148 'uk': 'Мова цього веб-сайту не може бути визначена автоматично. Будь ласка, вкажіть головну мову:' 149 }, 150 151 /** 152 * @name Hyphenator-basePath 153 * @description 154 * A string storing the basepath from where Hyphenator.js was loaded. 155 * This is used to load the patternfiles. 156 * The basepath is determined dynamically by searching all script-tags for Hyphenator.js 157 * If the path cannot be determined http://hyphenator.googlecode.com/svn/trunk/ is used as fallback. 158 * @type {string} 159 * @private 160 * @see Hyphenator-loadPatterns 161 */ 162 basePath = (function () { 163 var s = document.getElementsByTagName('script'), i = 0, p, src, t; 164 while (!!(t = s[i++])) { 165 if (!t.src) { 166 continue; 167 } 168 src = t.src; 169 p = src.indexOf('Hyphenator.js'); 170 if (p !== -1) { 171 return src.substring(0, p); 172 } 173 } 174 return 'http://hyphenator.googlecode.com/svn/trunk/'; 175 }()), 176 177 /** 178 * @name Hyphenator-isLocal 179 * @description 180 * isLocal is true, if Hyphenator is loaded from the same domain, as the webpage, but false, if 181 * it's loaded from an external source (i.e. directly from google.code) 182 */ 183 isLocal = (function () { 184 var re = false; 185 if (window.location.href.indexOf(basePath) !== -1) { 186 re = true; 187 } 188 return re; 189 }()), 190 191 /** 192 * @name Hyphenator-documentLoaded 193 * @description 194 * documentLoaded is true, when the DOM has been loaded. This is set by runOnContentLoaded 195 */ 196 documentLoaded = false, 197 documentCount = 0, 198 199 200 /** 201 * @name Hyphenator-contextWindow 202 * @description 203 * contextWindow stores the window for the document to be hyphenated. 204 * If there are frames this will change. 205 * So use contextWindow instead of window! 206 */ 207 contextWindow = window, 208 209 /** 210 * @name Hyphenator-doFrames 211 * @description 212 * switch to control if frames/iframes should be hyphenated, too 213 * defaults to false (frames are a bag of hurt!) 214 */ 215 doFrames = false, 216 217 /** 218 * @name Hyphenator-dontHyphenate 219 * @description 220 * A key-value object containing all html-tags whose content should not be hyphenated 221 * @type {Object.<string,boolean>} 222 * @private 223 * @see Hyphenator-hyphenateElement 224 */ 225 dontHyphenate = {'script': true, 'code': true, 'pre': true, 'img': true, 'br': true, 'samp': true, 'kbd': true, 'var': true, 'abbr': true, 'acronym': true, 'sub': true, 'sup': true, 'button': true, 'option': true, 'label': true, 'textarea': true, 'input': true}, 226 227 /** 228 * @name Hyphenator-enableCache 229 * @description 230 * A variable to set if caching is enabled or not 231 * @type boolean 232 * @default true 233 * @private 234 * @see Hyphenator.config 235 * @see hyphenateWord 236 */ 237 enableCache = true, 238 239 /** 240 * @name Hyphenator-storageType 241 * @description 242 * A variable to define what html5-DOM-Storage-Method is used ('none', 'local' or 'session') 243 * @type {string} 244 * @default 'none' 245 * @private 246 * @see Hyphenator.config 247 */ 248 storageType = 'local', 249 250 /** 251 * @name Hyphenator-storage 252 * @description 253 * An alias to the storage-Method defined in storageType. 254 * Set by Hyphenator.run() 255 * @type {Object|undefined} 256 * @default null 257 * @private 258 * @see Hyphenator.run 259 */ 260 storage, 261 262 /** 263 * @name Hyphenator-enableReducedPatternSet 264 * @description 265 * A variable to set if storing the used patterns is set 266 * @type boolean 267 * @default false 268 * @private 269 * @see Hyphenator.config 270 * @see hyphenateWord 271 * @see Hyphenator.getRedPatternSet 272 */ 273 enableReducedPatternSet = false, 274 275 /** 276 * @name Hyphenator-enableRemoteLoading 277 * @description 278 * A variable to set if pattern files should be loaded remotely or not 279 * @type boolean 280 * @default true 281 * @private 282 * @see Hyphenator.config 283 * @see Hyphenator-loadPatterns 284 */ 285 enableRemoteLoading = true, 286 287 /** 288 * @name Hyphenator-displayToggleBox 289 * @description 290 * A variable to set if the togglebox should be displayed or not 291 * @type boolean 292 * @default false 293 * @private 294 * @see Hyphenator.config 295 * @see Hyphenator-toggleBox 296 */ 297 displayToggleBox = false, 298 299 /** 300 * @name Hyphenator-hyphenateClass 301 * @description 302 * A string containing the css-class-name for the hyphenate class 303 * @type {string} 304 * @default 'hyphenate' 305 * @private 306 * @example 307 * <p class = "hyphenate">Text</p> 308 * @see Hyphenator.config 309 */ 310 hyphenateClass = 'hyphenate', 311 312 /** 313 * @name Hyphenator-dontHyphenateClass 314 * @description 315 * A string containing the css-class-name for elements that should not be hyphenated 316 * @type {string} 317 * @default 'donthyphenate' 318 * @private 319 * @example 320 * <p class = "donthyphenate">Text</p> 321 * @see Hyphenator.config 322 */ 323 dontHyphenateClass = 'donthyphenate', 324 325 /** 326 * @name Hyphenator-min 327 * @description 328 * A number wich indicates the minimal length of words to hyphenate. 329 * @type {number} 330 * @default 6 331 * @private 332 * @see Hyphenator.config 333 */ 334 min = 6, 335 336 /** 337 * @name Hyphenator-orphanControl 338 * @description 339 * Control how the last words of a line are handled: 340 * level 1 (default): last word is hyphenated 341 * level 2: last word is not hyphenated 342 * level 3: last word is not hyphenated and last space is non breaking 343 * @type {number} 344 * @default 1 345 * @private 346 */ 347 orphanControl = 1, 348 349 /** 350 * @name Hyphenator-isBookmarklet 351 * @description 352 * Indicates if Hyphanetor runs as bookmarklet or not. 353 * @type boolean 354 * @default false 355 * @private 356 */ 357 isBookmarklet = (function () { 358 var loc = null, re = false, jsArray = document.getElementsByTagName('script'), i, l; 359 for (i = 0, l = jsArray.length; i < l; i++) { 360 if (!!jsArray[i].getAttribute('src')) { 361 loc = jsArray[i].getAttribute('src'); 362 } 363 if (!loc) { 364 continue; 365 } else if (loc.indexOf('Hyphenator.js?bm=true') !== -1) { 366 re = true; 367 } 368 } 369 return re; 370 }()), 371 372 /** 373 * @name Hyphenator-mainLanguage 374 * @description 375 * The general language of the document 376 * @type {string|null} 377 * @private 378 * @see Hyphenator-autoSetMainLanguage 379 */ 380 mainLanguage = null, 381 382 /** 383 * @name Hyphenator-elements 384 * @description 385 * An array holding all elements that have to be hyphenated. This var is filled by 386 * {@link Hyphenator-gatherDocumentInfos} 387 * @type {Array} 388 * @private 389 */ 390 elements = [], 391 392 /** 393 * @name Hyphenator-exceptions 394 * @description 395 * An object containing exceptions as comma separated strings for each language. 396 * When the language-objects are loaded, their exceptions are processed, copied here and then deleted. 397 * @see Hyphenator-prepareLanguagesObj 398 * @type {Object} 399 * @private 400 */ 401 exceptions = {}, 402 403 /** 404 * @name Hyphenator-docLanguages 405 * @description 406 * An object holding all languages used in the document. This is filled by 407 * {@link Hyphenator-gatherDocumentInfos} 408 * @type {Object} 409 * @private 410 */ 411 docLanguages = {}, 412 413 414 /** 415 * @name Hyphenator-state 416 * @description 417 * A number that inidcates the current state of the script 418 * 0: not initialized 419 * 1: loading patterns 420 * 2: ready 421 * 3: hyphenation done 422 * 4: hyphenation removed 423 * @type {number} 424 * @private 425 */ 426 state = 0, 427 428 /** 429 * @name Hyphenator-url 430 * @description 431 * A string containing a RegularExpression to match URL's 432 * @type {string} 433 * @private 434 */ 435 url = '(\\w*:\/\/)?((\\w*:)?(\\w*)@)?((([\\d]{1,3}\\.){3}([\\d]{1,3}))|((www\\.|[a-zA-Z]\\.)?[a-zA-Z0-9\\-\\.]+\\.([a-z]{2,4})))(:\\d*)?(\/[\\w#!:\\.?\\+=&%@!\\-]*)*', 436 // protocoll usr pwd ip or host tld port path 437 /** 438 * @name Hyphenator-mail 439 * @description 440 * A string containing a RegularExpression to match mail-adresses 441 * @type {string} 442 * @private 443 */ 444 mail = '[\\w-\\.]+@[\\w\\.]+', 445 446 /** 447 * @name Hyphenator-urlRE 448 * @description 449 * A RegularExpressions-Object for url- and mail adress matching 450 * @type {RegExp} 451 * @private 452 */ 453 urlOrMailRE = new RegExp('(' + url + ')|(' + mail + ')', 'i'), 454 455 /** 456 * @name Hyphenator-zeroWidthSpace 457 * @description 458 * A string that holds a char. 459 * Depending on the browser, this is the zero with space or an empty string. 460 * zeroWidthSpace is used to break URLs 461 * @type {string} 462 * @private 463 */ 464 zeroWidthSpace = (function () { 465 var zws, ua = navigator.userAgent.toLowerCase(); 466 zws = String.fromCharCode(8203); //Unicode zero width space 467 if (ua.indexOf('msie 6') !== -1) { 468 zws = ''; //IE6 doesn't support zws 469 } 470 if (ua.indexOf('opera') !== -1 && ua.indexOf('version/10.00') !== -1) { 471 zws = ''; //opera 10 on XP doesn't support zws 472 } 473 return zws; 474 }()), 475 476 /** 477 * @name Hyphenator-createElem 478 * @description 479 * A function alias to document.createElementNS or document.createElement 480 * @type {function(string, Object)} 481 * @private 482 */ 483 createElem = function (tagname, context) { 484 context = context || contextWindow; 485 if (document.createElementNS) { 486 return context.document.createElementNS('http://www.w3.org/1999/xhtml', tagname); 487 } else if (document.createElement) { 488 return context.document.createElement(tagname); 489 } 490 }, 491 492 /** 493 * @name Hyphenator-onHyphenationDone 494 * @description 495 * A method to be called, when the last element has been hyphenated or the hyphenation has been 496 * removed from the last element. 497 * @see Hyphenator.config 498 * @type {function()} 499 * @private 500 */ 501 onHyphenationDone = function () {}, 502 503 /** 504 * @name Hyphenator-onError 505 * @description 506 * A function that can be called upon an error. 507 * @see Hyphenator.config 508 * @type {function(Object)} 509 * @private 510 */ 511 onError = function (e) { 512 window.alert("Hyphenator.js says:\n\nAn Error ocurred:\n" + e.message); 513 }, 514 515 /** 516 * @name Hyphenator-selectorFunction 517 * @description 518 * A function that has to return a HTMLNodeList of Elements to be hyphenated. 519 * By default it uses the classname ('hyphenate') to select the elements. 520 * @see Hyphenator.config 521 * @type {function()} 522 * @private 523 */ 524 selectorFunction = function () { 525 var tmp, el = [], i, l; 526 if (document.getElementsByClassName) { 527 el = contextWindow.document.getElementsByClassName(hyphenateClass); 528 } else { 529 tmp = contextWindow.document.getElementsByTagName('*'); 530 l = tmp.length; 531 for (i = 0; i < l; i++) 532 { 533 if (tmp[i].className.indexOf(hyphenateClass) !== -1 && tmp[i].className.indexOf(dontHyphenateClass) === -1) { 534 el.push(tmp[i]); 535 } 536 } 537 } 538 return el; 539 }, 540 541 /** 542 * @name Hyphenator-intermediateState 543 * @description 544 * The value of style.visibility of the text while it is hyphenated. 545 * @see Hyphenator.config 546 * @type {string} 547 * @private 548 */ 549 intermediateState = 'hidden', 550 551 /** 552 * @name Hyphenator-hyphen 553 * @description 554 * A string containing the character for in-word-hyphenation 555 * @type {string} 556 * @default the soft hyphen 557 * @private 558 * @see Hyphenator.config 559 */ 560 hyphen = String.fromCharCode(173), 561 562 /** 563 * @name Hyphenator-urlhyphen 564 * @description 565 * A string containing the character for url/mail-hyphenation 566 * @type {string} 567 * @default the zero width space 568 * @private 569 * @see Hyphenator.config 570 * @see Hyphenator-zeroWidthSpace 571 */ 572 urlhyphen = zeroWidthSpace, 573 574 /** 575 * @name Hyphenator-safeCopy 576 * @description 577 * Defines wether work-around for copy issues is active or not 578 * Not supported by Opera (no onCopy handler) 579 * @type boolean 580 * @default true 581 * @private 582 * @see Hyphenator.config 583 * @see Hyphenator-registerOnCopy 584 */ 585 safeCopy = true, 586 587 /** 588 * @name Hyphenator-Expando 589 * @description 590 * This custom object stores data for elements: storing data directly in elements 591 * (DomElement.customData = foobar;) isn't a good idea. It would lead to conflicts 592 * in form elements, when the form has a child with name="foobar". Therefore, this 593 * solution follows the approach of jQuery: the data is stored in an object and 594 * referenced by a unique attribute of the element. The attribute has a name that 595 * is built by the prefix "HyphenatorExpando_" and a random number, so if the very 596 * very rare case occurs, that there's already an attribute with the same name, a 597 * simple reload is enough to make it function. 598 * @private 599 */ 600 Expando = (function () { 601 var container = {}, 602 name = "HyphenatorExpando_" + Math.random(), 603 uuid = 0; 604 return { 605 getDataForElem : function (elem) { 606 return container[elem[name].id]; 607 }, 608 setDataForElem : function (elem, data) { 609 var id; 610 if (elem[name] && elem[name].id !== '') { 611 id = elem[name].id; 612 } else { 613 id = uuid++; 614 elem[name] = {'id': id}; //object needed, otherways it is reflected in HTML in IE 615 } 616 container[id] = data; 617 }, 618 appendDataForElem : function (elem, data) { 619 var k; 620 for (k in data) { 621 if (data.hasOwnProperty(k)) { 622 container[elem[name].id][k] = data[k]; 623 } 624 } 625 }, 626 delDataOfElem : function (elem) { 627 delete container[elem[name]]; 628 } 629 }; 630 }()), 631 632 /* 633 * runOnContentLoaded is based od jQuery.bindReady() 634 * see 635 * jQuery JavaScript Library v1.3.2 636 * http://jquery.com/ 637 * 638 * Copyright (c) 2009 John Resig 639 * Dual licensed under the MIT and GPL licenses. 640 * http://docs.jquery.com/License 641 * 642 * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 643 * Revision: 6246 644 */ 645 /** 646 * @name Hyphenator-runOnContentLoaded 647 * @description 648 * A crossbrowser solution for the DOMContentLoaded-Event based on jQuery 649 * <a href = "http://jquery.com/</a> 650 * I added some functionality: e.g. support for frames and iframes… 651 * @param {Object} w the window-object 652 * @param {function()} f the function to call onDOMContentLoaded 653 * @private 654 */ 655 runOnContentLoaded = function (w, f) { 656 var DOMContentLoaded = function () {}, toplevel, hyphRunForThis = {}; 657 if (documentLoaded && !hyphRunForThis[w.location.href]) { 658 f(); 659 hyphRunForThis[w.location.href] = true; 660 return; 661 } 662 function init(context) { 663 contextWindow = context || window; 664 if (!hyphRunForThis[contextWindow.location.href] && (!documentLoaded || contextWindow != window.parent)) { 665 documentLoaded = true; 666 f(); 667 hyphRunForThis[contextWindow.location.href] = true; 668 } 669 } 670 671 function doScrollCheck() { 672 try { 673 // If IE is used, use the trick by Diego Perini 674 // http://javascript.nwbox.com/IEContentLoaded/ 675 document.documentElement.doScroll("left"); 676 } catch (error) { 677 setTimeout(doScrollCheck, 1); 678 return; 679 } 680 681 // and execute any waiting functions 682 init(window); 683 } 684 685 function doOnLoad() { 686 var i, haveAccess, fl = window.frames.length; 687 if (doFrames && fl > 0) { 688 for (i = 0; i < fl; i++) { 689 haveAccess = undefined; 690 //try catch isn't enough for webkit 691 try { 692 //opera throws only on document.toString-access 693 haveAccess = window.frames[i].document.toString(); 694 } catch (e) { 695 haveAccess = undefined; 696 } 697 if (!!haveAccess) { 698 init(window.frames[i]); 699 } 700 } 701 contextWindow = window; 702 f(); 703 hyphRunForThis[window.location.href] = true; 704 } else { 705 init(window); 706 } 707 } 708 709 // Cleanup functions for the document ready method 710 if (document.addEventListener) { 711 DOMContentLoaded = function () { 712 document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); 713 if (doFrames && window.frames.length > 0) { 714 //we are in a frameset, so do nothing but wait for onload to fire 715 return; 716 } else { 717 init(window); 718 } 719 }; 720 721 } else if (document.attachEvent) { 722 DOMContentLoaded = function () { 723 // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). 724 if (document.readyState === "complete") { 725 document.detachEvent("onreadystatechange", DOMContentLoaded); 726 if (doFrames && window.frames.length > 0) { 727 //we are in a frameset, so do nothing but wait for onload to fire 728 return; 729 } else { 730 init(window); 731 } 732 } 733 }; 734 } 735 736 // Mozilla, Opera and webkit nightlies currently support this event 737 if (document.addEventListener) { 738 // Use the handy event callback 739 document.addEventListener("DOMContentLoaded", DOMContentLoaded, false); 740 741 // A fallback to window.onload, that will always work 742 window.addEventListener("load", doOnLoad, false); 743 744 // If IE event model is used 745 } else if (document.attachEvent) { 746 // ensure firing before onload, 747 // maybe late but safe also for iframes 748 document.attachEvent("onreadystatechange", DOMContentLoaded); 749 750 // A fallback to window.onload, that will always work 751 window.attachEvent("onload", doOnLoad); 752 753 // If IE and not a frame 754 // continually check to see if the document is ready 755 toplevel = false; 756 try { 757 toplevel = window.frameElement === null; 758 } catch (e) {} 759 760 if (document.documentElement.doScroll && toplevel) { 761 doScrollCheck(); 762 } 763 } 764 765 }, 766 767 768 769 /** 770 * @name Hyphenator-getLang 771 * @description 772 * Gets the language of an element. If no language is set, it may use the {@link Hyphenator-mainLanguage}. 773 * @param {Object} el The first parameter is an DOM-Element-Object 774 * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator-mainLanguage} 775 * if there's no language found for the element. 776 * @private 777 */ 778 getLang = function (el, fallback) { 779 if (!!el.getAttribute('lang')) { 780 return el.getAttribute('lang').toLowerCase(); 781 } 782 // The following doesn't work in IE due to a bug when getAttribute('xml:lang') in a table 783 /*if (!!el.getAttribute('xml:lang')) { 784 return el.getAttribute('xml:lang').substring(0, 2); 785 }*/ 786 //instead, we have to do this (thanks to borgzor): 787 try { 788 if (!!el.getAttribute('xml:lang')) { 789 return el.getAttribute('xml:lang').toLowerCase(); 790 } 791 } catch (ex) {} 792 if (el.tagName !== 'HTML') { 793 return getLang(el.parentNode, true); 794 } 795 if (fallback) { 796 return mainLanguage; 797 } 798 return null; 799 }, 800 801 /** 802 * @name Hyphenator-autoSetMainLanguage 803 * @description 804 * Retrieves the language of the document from the DOM. 805 * The function looks in the following places: 806 * <ul> 807 * <li>lang-attribute in the html-tag</li> 808 * <li><meta http-equiv = "content-language" content = "xy" /></li> 809 * <li><meta name = "DC.Language" content = "xy" /></li> 810 * <li><meta name = "language" content = "xy" /></li> 811 * </li> 812 * If nothing can be found a prompt using {@link Hyphenator-languageHint} and {@link Hyphenator-prompterStrings} is displayed. 813 * If the retrieved language is in the object {@link Hyphenator-supportedLang} it is copied to {@link Hyphenator-mainLanguage} 814 * @private 815 */ 816 autoSetMainLanguage = function (w) { 817 w = w || contextWindow; 818 var el = w.document.getElementsByTagName('html')[0], 819 m = w.document.getElementsByTagName('meta'), 820 i, text, e, ul; 821 mainLanguage = getLang(el, false); 822 if (!mainLanguage) { 823 for (i = 0; i < m.length; i++) { 824 //<meta http-equiv = "content-language" content="xy"> 825 if (!!m[i].getAttribute('http-equiv') && (m[i].getAttribute('http-equiv').toLowerCase() === 'content-language')) { 826 mainLanguage = m[i].getAttribute('content').toLowerCase(); 827 } 828 //<meta name = "DC.Language" content="xy"> 829 if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'dc.language')) { 830 mainLanguage = m[i].getAttribute('content').toLowerCase(); 831 } 832 //<meta name = "language" content = "xy"> 833 if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'language')) { 834 mainLanguage = m[i].getAttribute('content').toLowerCase(); 835 } 836 } 837 } 838 if (!mainLanguage && doFrames && contextWindow != window.parent) { 839 autoSetMainLanguage(window.parent); 840 } 841 if (!mainLanguage) { 842 text = ''; 843 ul = navigator.language ? navigator.language : navigator.userLanguage; 844 ul = ul.substring(0, 2); 845 if (prompterStrings.hasOwnProperty(ul)) { 846 text = prompterStrings[ul]; 847 } else { 848 text = prompterStrings.en; 849 } 850 text += ' (ISO 639-1)\n\n' + languageHint; 851 mainLanguage = window.prompt(unescape(text), ul).toLowerCase(); 852 } 853 if (!supportedLang.hasOwnProperty(mainLanguage)) { 854 if (supportedLang.hasOwnProperty(mainLanguage.split('-')[0])) { //try subtag 855 mainLanguage = mainLanguage.split('-')[0]; 856 } else { 857 e = new Error('The language "' + mainLanguage + '" is not yet supported.'); 858 throw e; 859 } 860 } 861 }, 862 863 /** 864 * @name Hyphenator-gatherDocumentInfos 865 * @description 866 * This method runs through the DOM and executes the process()-function on: 867 * - every node returned by the {@link Hyphenator-selectorFunction}. 868 * The process()-function copies the element to the elements-variable, sets its visibility 869 * to intermediateState, retrieves its language and recursivly descends the DOM-tree until 870 * the child-Nodes aren't of type 1 871 * @private 872 */ 873 gatherDocumentInfos = function () { 874 var elToProcess, tmp, i = 0, 875 process = function (el, hide, lang) { 876 var n, i = 0, hyphenatorSettings = {}; 877 if (hide && intermediateState === 'hidden') { 878 if (!!el.getAttribute('style')) { 879 hyphenatorSettings.hasOwnStyle = true; 880 } else { 881 hyphenatorSettings.hasOwnStyle = false; 882 } 883 hyphenatorSettings.isHidden = true; 884 el.style.visibility = 'hidden'; 885 } 886 if (el.lang && typeof(el.lang) === 'string') { 887 hyphenatorSettings.language = el.lang.toLowerCase(); //copy attribute-lang to internal lang 888 } else if (lang) { 889 hyphenatorSettings.language = lang.toLowerCase(); 890 } else { 891 hyphenatorSettings.language = getLang(el, true); 892 } 893 lang = hyphenatorSettings.language; 894 if (supportedLang[lang]) { 895 docLanguages[lang] = true; 896 } else { 897 if (supportedLang.hasOwnProperty(lang.split('-')[0])) { //try subtag 898 lang = lang.split('-')[0]; 899 hyphenatorSettings.language = lang; 900 } else if (!isBookmarklet) { 901 onError(new Error('Language ' + lang + ' is not yet supported.')); 902 } 903 } 904 Expando.setDataForElem(el, hyphenatorSettings); 905 elements.push(el); 906 while (!!(n = el.childNodes[i++])) { 907 if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] && 908 n.className.indexOf(dontHyphenateClass) === -1 && !(n in elToProcess)) { 909 process(n, false, lang); 910 } 911 } 912 }; 913 if (isBookmarklet) { 914 elToProcess = contextWindow.document.getElementsByTagName('body')[0]; 915 process(elToProcess, false, mainLanguage); 916 } else { 917 elToProcess = selectorFunction(); 918 while (!!(tmp = elToProcess[i++])) 919 { 920 process(tmp, true, ''); 921 } 922 } 923 if (!Hyphenator.languages.hasOwnProperty(mainLanguage)) { 924 docLanguages[mainLanguage] = true; 925 } else if (!Hyphenator.languages[mainLanguage].prepared) { 926 docLanguages[mainLanguage] = true; 927 } 928 if (elements.length > 0) { 929 Expando.appendDataForElem(elements[elements.length - 1], {isLast : true}); 930 } 931 }, 932 933 /** 934 * @name Hyphenator-convertPatterns 935 * @description 936 * Converts the patterns from string '_a6' to object '_a':'_a6'. 937 * The result is stored in the {@link Hyphenator-patterns}-object. 938 * @private 939 * @param {string} lang the language whose patterns shall be converted 940 */ 941 convertPatterns = function (lang) { 942 var plen, anfang, ende, pats, pat, key, tmp = {}; 943 pats = Hyphenator.languages[lang].patterns; 944 for (plen in pats) { 945 if (pats.hasOwnProperty(plen)) { 946 plen = parseInt(plen, 10); 947 anfang = 0; 948 ende = plen; 949 while (!!(pat = pats[plen].substring(anfang, ende))) { 950 key = pat.replace(/\d/g, ''); 951 tmp[key] = pat; 952 anfang = ende; 953 ende += plen; 954 } 955 } 956 } 957 Hyphenator.languages[lang].patterns = tmp; 958 Hyphenator.languages[lang].patternsConverted = true; 959 }, 960 961 /** 962 * @name Hyphenator-convertExceptionsToObject 963 * @description 964 * Converts a list of comma seprated exceptions to an object: 965 * 'Fortran,Hy-phen-a-tion' -> {'Fortran':'Fortran','Hyphenation':'Hy-phen-a-tion'} 966 * @private 967 * @param {string} exc a comma separated string of exceptions (without spaces) 968 */ 969 convertExceptionsToObject = function (exc) { 970 var w = exc.split(', '), 971 r = {}, 972 i, l, key; 973 for (i = 0, l = w.length; i < l; i++) { 974 key = w[i].replace(/-/g, ''); 975 if (!r.hasOwnProperty(key)) { 976 r[key] = w[i]; 977 } 978 } 979 return r; 980 }, 981 982 /** 983 * @name Hyphenator-loadPatterns 984 * @description 985 * Adds a <script>-Tag to the DOM to load an externeal .js-file containing patterns and settings for the given language. 986 * If the given language is not in the {@link Hyphenator-supportedLang}-Object it returns. 987 * One may ask why we are not using AJAX to load the patterns. The XMLHttpRequest-Object 988 * has a same-origin-policy. This makes the isBookmarklet-functionality impossible. 989 * @param {string} lang The language to load the patterns for 990 * @private 991 * @see Hyphenator-basePath 992 */ 993 loadPatterns = function (lang) { 994 var url, xhr, head, script; 995 if (supportedLang[lang] && !Hyphenator.languages[lang]) { 996 url = basePath + 'patterns/' + supportedLang[lang]; 997 } else { 998 return; 999 } 1000 if (isLocal && !isBookmarklet) { 1001 //check if 'url' is available: 1002 xhr = null; 1003 if (typeof XMLHttpRequest !== 'undefined') { 1004 xhr = new XMLHttpRequest(); 1005 } 1006 if (!xhr) { 1007 try { 1008 xhr = new ActiveXObject("Msxml2.XMLHTTP"); 1009 } catch (e) { 1010 xhr = null; 1011 } 1012 } 1013 if (xhr) { 1014 xhr.open('HEAD', url, false); 1015 xhr.setRequestHeader('Cache-Control', 'no-cache'); 1016 xhr.send(null); 1017 if (xhr.status === 404) { 1018 onError(new Error('Could not load\n' + url)); 1019 delete docLanguages[lang]; 1020 return; 1021 } 1022 } 1023 } 1024 if (createElem) { 1025 head = window.document.getElementsByTagName('head').item(0); 1026 script = createElem('script', window); 1027 script.src = url; 1028 script.type = 'text/javascript'; 1029 head.appendChild(script); 1030 } 1031 }, 1032 1033 /** 1034 * @name Hyphenator-prepareLanguagesObj 1035 * @description 1036 * Adds a cache to each language and converts the exceptions-list to an object. 1037 * If storage is active the object is stored there. 1038 * @private 1039 * @param {string} lang the language ob the lang-obj 1040 */ 1041 prepareLanguagesObj = function (lang) { 1042 var lo = Hyphenator.languages[lang], wrd; 1043 if (!lo.prepared) { 1044 if (enableCache) { 1045 lo.cache = {}; 1046 //Export 1047 lo['cache'] = lo.cache; 1048 } 1049 if (enableReducedPatternSet) { 1050 lo.redPatSet = {}; 1051 } 1052 //add exceptions from the pattern file to the local 'exceptions'-obj 1053 if (lo.hasOwnProperty('exceptions')) { 1054 Hyphenator.addExceptions(lang, lo.exceptions); 1055 delete lo.exceptions; 1056 } 1057 //copy global exceptions to the language specific exceptions 1058 if (exceptions.hasOwnProperty('global')) { 1059 if (exceptions.hasOwnProperty(lang)) { 1060 exceptions[lang] += ', ' + exceptions.global; 1061 } else { 1062 exceptions[lang] = exceptions.global; 1063 } 1064 } 1065 //move exceptions from the the local 'exceptions'-obj to the 'language'-object 1066 if (exceptions.hasOwnProperty(lang)) { 1067 lo.exceptions = convertExceptionsToObject(exceptions[lang]); 1068 delete exceptions[lang]; 1069 } else { 1070 lo.exceptions = {}; 1071 } 1072 convertPatterns(lang); 1073 wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + '-]{' + min + ',}'; 1074 lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi'); 1075 lo.prepared = true; 1076 } 1077 if (storage) { 1078 try { 1079 storage.setItem('Hyphenator_' + lang, window.JSON.stringify(lo)); 1080 } catch (e) { 1081 //onError(e); 1082 } 1083 } 1084 1085 }, 1086 1087 /** 1088 * @name Hyphenator-prepare 1089 * @description 1090 * This funtion prepares the Hyphenator-Object: If RemoteLoading is turned off, it assumes 1091 * that the patternfiles are loaded, all conversions are made and the callback is called. 1092 * If storage is active the object is retrieved there. 1093 * If RemoteLoading is on (default), it loads the pattern files and waits until they are loaded, 1094 * by repeatedly checking Hyphenator.languages. If a patterfile is loaded the patterns are 1095 * converted to their object style and the lang-object extended. 1096 * Finally the callback is called. 1097 * @param {function()} callback to call, when all patterns are loaded 1098 * @private 1099 */ 1100 prepare = function (callback) { 1101 var lang, languagesToLoad = 0, interval, tmp1, tmp2; 1102 if (!enableRemoteLoading) { 1103 for (lang in Hyphenator.languages) { 1104 if (Hyphenator.languages.hasOwnProperty(lang)) { 1105 prepareLanguagesObj(lang); 1106 } 1107 } 1108 state = 2; 1109 callback(); 1110 return; 1111 } 1112 // get all languages that are used and preload the patterns 1113 state = 1; 1114 for (lang in docLanguages) { 1115 if (docLanguages.hasOwnProperty(lang)) { 1116 ++languagesToLoad; 1117 if (storage) { 1118 if (storage.getItem('Hyphenator_' + lang)) { 1119 Hyphenator.languages[lang] = window.JSON.parse(storage.getItem('Hyphenator_' + lang)); 1120 if (exceptions.hasOwnProperty('global')) { 1121 tmp1 = convertExceptionsToObject(exceptions.global); 1122 for (tmp2 in tmp1) { 1123 if (tmp1.hasOwnProperty(tmp2)) { 1124 Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2]; 1125 } 1126 } 1127 } 1128 //Replace exceptions since they may have been changed: 1129 if (exceptions.hasOwnProperty(lang)) { 1130 tmp1 = convertExceptionsToObject(exceptions[lang]); 1131 for (tmp2 in tmp1) { 1132 if (tmp1.hasOwnProperty(tmp2)) { 1133 Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2]; 1134 } 1135 } 1136 delete exceptions[lang]; 1137 } 1138 //Replace genRegExp since it may have been changed: 1139 tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + '@' + String.fromCharCode(173) + '-]{' + min + ',}'; 1140 Hyphenator.languages[lang].genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + tmp1 + ')', 'gi'); 1141 1142 delete docLanguages[lang]; 1143 --languagesToLoad; 1144 continue; 1145 } 1146 } 1147 loadPatterns(lang); 1148 } 1149 } 1150 if (languagesToLoad === 0) { 1151 state = 2; 1152 callback(); 1153 return; 1154 } 1155 // wait until they are loaded 1156 interval = window.setInterval(function () { 1157 var finishedLoading = false, lang; 1158 for (lang in docLanguages) { 1159 if (docLanguages.hasOwnProperty(lang)) { 1160 if (!Hyphenator.languages[lang]) { 1161 finishedLoading = false; 1162 break; 1163 } else { 1164 finishedLoading = true; 1165 delete docLanguages[lang]; 1166 //do conversion while other patterns are loading: 1167 prepareLanguagesObj(lang); 1168 } 1169 } 1170 } 1171 if (finishedLoading) { 1172 window.clearInterval(interval); 1173 state = 2; 1174 callback(); 1175 } 1176 }, 100); 1177 }, 1178 1179 /** 1180 * @name Hyphenator-switchToggleBox 1181 * @description 1182 * Creates or hides the toggleBox: a small button to turn off/on hyphenation on a page. 1183 * @param {boolean} s true when hyphenation is on, false when it's off 1184 * @see Hyphenator.config 1185 * @private 1186 */ 1187 toggleBox = function (s) { 1188 var myBox, bdy, myIdAttribute, myTextNode, myClassAttribute; 1189 if (!!(myBox = contextWindow.document.getElementById('HyphenatorToggleBox'))) { 1190 if (s) { 1191 myBox.firstChild.data = 'Hy-phe-na-ti-on'; 1192 } else { 1193 myBox.firstChild.data = 'Hyphenation'; 1194 } 1195 } else { 1196 bdy = contextWindow.document.getElementsByTagName('body')[0]; 1197 myBox = createElem('div', contextWindow); 1198 myIdAttribute = contextWindow.document.createAttribute('id'); 1199 myIdAttribute.nodeValue = 'HyphenatorToggleBox'; 1200 myClassAttribute = contextWindow.document.createAttribute('class'); 1201 myClassAttribute.nodeValue = dontHyphenateClass; 1202 myTextNode = contextWindow.document.createTextNode('Hy-phe-na-ti-on'); 1203 myBox.appendChild(myTextNode); 1204 myBox.setAttributeNode(myIdAttribute); 1205 myBox.setAttributeNode(myClassAttribute); 1206 myBox.onclick = Hyphenator.toggleHyphenation; 1207 myBox.style.position = 'absolute'; 1208 myBox.style.top = '0px'; 1209 myBox.style.right = '0px'; 1210 myBox.style.margin = '0'; 1211 myBox.style.backgroundColor = '#AAAAAA'; 1212 myBox.style.color = '#FFFFFF'; 1213 myBox.style.font = '6pt Arial'; 1214 myBox.style.letterSpacing = '0.2em'; 1215 myBox.style.padding = '3px'; 1216 myBox.style.cursor = 'pointer'; 1217 myBox.style.WebkitBorderBottomLeftRadius = '4px'; 1218 myBox.style.MozBorderRadiusBottomleft = '4px'; 1219 bdy.appendChild(myBox); 1220 } 1221 }, 1222 1223 /** 1224 * @name Hyphenator-hyphenateWord 1225 * @description 1226 * This function is the heart of Hyphenator.js. It returns a hyphenated word. 1227 * 1228 * If there's already a {@link Hyphenator-hypen} in the word, the word is returned as it is. 1229 * If the word is in the exceptions list or in the cache, it is retrieved from it. 1230 * If there's a '-' put a zeroWidthSpace after the '-' and hyphenate the parts. 1231 * @param {string} lang The language of the word 1232 * @param {string} word The word 1233 * @returns string The hyphenated word 1234 * @public 1235 */ 1236 hyphenateWord = function (lang, word) { 1237 var lo = Hyphenator.languages[lang], 1238 parts, i, l, w, wl, s, hypos, p, maxwins, win, pat = false, patk, c, t, n, numb3rs, inserted, hyphenatedword, val; 1239 if (word === '') { 1240 return ''; 1241 } 1242 if (word.indexOf(hyphen) !== -1) { 1243 //word already contains shy; -> leave at it is! 1244 return word; 1245 } 1246 if (enableCache && lo.cache.hasOwnProperty(word)) { //the word is in the cache 1247 return lo.cache[word]; 1248 } 1249 if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list 1250 return lo.exceptions[word].replace(/-/g, hyphen); 1251 } 1252 if (word.indexOf('-') !== -1) { 1253 //word contains '-' -> hyphenate the parts separated with '-' 1254 parts = word.split('-'); 1255 for (i = 0, l = parts.length; i < l; i++) { 1256 parts[i] = hyphenateWord(lang, parts[i]); 1257 } 1258 return parts.join('-'); 1259 } 1260 //finally the core hyphenation algorithm 1261 w = '_' + word + '_'; 1262 wl = w.length; 1263 s = w.split(''); 1264 if (word.indexOf("'") !== -1) { 1265 w = w.toLowerCase().replace("'", "’"); //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns) 1266 } else { 1267 w = w.toLowerCase(); 1268 } 1269 hypos = []; 1270 numb3rs = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}; //check for member is faster then isFinite() 1271 n = wl - lo.shortestPattern; 1272 for (p = 0; p <= n; p++) { 1273 maxwins = Math.min((wl - p), lo.longestPattern); 1274 for (win = lo.shortestPattern; win <= maxwins; win++) { 1275 if (lo.patterns.hasOwnProperty(patk = w.substring(p, p + win))) { 1276 pat = lo.patterns[patk]; 1277 if (enableReducedPatternSet) { 1278 lo.redPatSet[patk] = pat; 1279 } 1280 if (typeof pat === 'string') { 1281 //convert from string 'a5b' to array [1,5] (pos,value) 1282 t = 0; 1283 val = []; 1284 for (i = 0; i < pat.length; i++) { 1285 if (!!(c = numb3rs[pat.charAt(i)])) { 1286 val.push(i - t, c); 1287 t++; 1288 } 1289 } 1290 pat = lo.patterns[patk] = val; 1291 } 1292 } else { 1293 continue; 1294 } 1295 for (i = 0; i < pat.length; i++) { 1296 c = p - 1 + pat[i]; 1297 if (!hypos[c] || hypos[c] < pat[i + 1]) { 1298 hypos[c] = pat[i + 1]; 1299 } 1300 i++; 1301 } 1302 } 1303 } 1304 inserted = 0; 1305 for (i = lo.leftmin; i <= (word.length - lo.rightmin); i++) { 1306 if (!!(hypos[i] & 1)) { 1307 s.splice(i + inserted + 1, 0, hyphen); 1308 inserted++; 1309 } 1310 } 1311 hyphenatedword = s.slice(1, -1).join(''); 1312 if (enableCache) { 1313 lo.cache[word] = hyphenatedword; 1314 } 1315 return hyphenatedword; 1316 }, 1317 1318 /** 1319 * @name Hyphenator-hyphenateURL 1320 * @description 1321 * Puts {@link Hyphenator-urlhyphen} after each no-alphanumeric char that my be in a URL. 1322 * @param {string} url to hyphenate 1323 * @returns string the hyphenated URL 1324 * @public 1325 */ 1326 hyphenateURL = function (url) { 1327 return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen); 1328 }, 1329 1330 /** 1331 * @name Hyphenator-hyphenateElement 1332 * @description 1333 * Takes the content of the given element and - if there's text - replaces the words 1334 * by hyphenated words. If there's another element, the function is called recursively. 1335 * When all words are hyphenated, the visibility of the element is set to 'visible'. 1336 * @param {Object} el The element to hyphenate 1337 * @public 1338 */ 1339 hyphenateElement = function (el) { 1340 var hyphenatorSettings = Expando.getDataForElem(el), 1341 lang = hyphenatorSettings.language, hyphenate, n, i, 1342 controlOrphans = function (part) { 1343 var h, r; 1344 switch (hyphen) { 1345 case '|': 1346 h = '\\|'; 1347 break; 1348 case '+': 1349 h = '\\+'; 1350 break; 1351 case '*': 1352 h = '\\*'; 1353 break; 1354 default: 1355 h = hyphen; 1356 } 1357 if (orphanControl >= 2) { 1358 //remove hyphen points from last word 1359 r = part.split(' '); 1360 r[1] = r[1].replace(new RegExp(h, 'g'), ''); 1361 r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), ''); 1362 r = r.join(' '); 1363 } 1364 if (orphanControl === 3) { 1365 //replace spaces by non breaking spaces 1366 r = r.replace(/[ ]+/g, String.fromCharCode(160)); 1367 } 1368 return r; 1369 }; 1370 if (Hyphenator.languages.hasOwnProperty(lang)) { 1371 hyphenate = function (word) { 1372 if (urlOrMailRE.test(word)) { 1373 return hyphenateURL(word); 1374 } else { 1375 return hyphenateWord(lang, word); 1376 } 1377 }; 1378 i = 0; 1379 while (!!(n = el.childNodes[i++])) { 1380 if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate! 1381 n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate); 1382 if (orphanControl !== 1) { 1383 n.data = n.data.replace(/[\S]+ [\S]+$/, controlOrphans); 1384 } 1385 } 1386 } 1387 } 1388 if (hyphenatorSettings.isHidden && intermediateState === 'hidden') { 1389 el.style.visibility = 'visible'; 1390 if (!hyphenatorSettings.hasOwnStyle) { 1391 el.setAttribute('style', ''); // without this, removeAttribute doesn't work in Safari (thanks to molily) 1392 el.removeAttribute('style'); 1393 } else { 1394 if (el.style.removeProperty) { 1395 el.style.removeProperty('visibility'); 1396 } else if (el.style.removeAttribute) { // IE 1397 el.style.removeAttribute('visibility'); 1398 } 1399 } 1400 } 1401 if (hyphenatorSettings.isLast) { 1402 state = 3; 1403 documentCount--; 1404 if (documentCount > (-1000) && documentCount <= 0) { 1405 documentCount = (-2000); 1406 onHyphenationDone(); 1407 } 1408 } 1409 }, 1410 1411 /** 1412 * @name Hyphenator-removeHyphenationFromElement 1413 * @description 1414 * Removes all hyphens from the element. If there are other elements, the function is 1415 * called recursively. 1416 * Removing hyphens is usefull if you like to copy text. Some browsers are buggy when the copy hyphenated texts. 1417 * @param {Object} el The element where to remove hyphenation. 1418 * @public 1419 */ 1420 removeHyphenationFromElement = function (el) { 1421 var h, i = 0, n; 1422 switch (hyphen) { 1423 case '|': 1424 h = '\\|'; 1425 break; 1426 case '+': 1427 h = '\\+'; 1428 break; 1429 case '*': 1430 h = '\\*'; 1431 break; 1432 default: 1433 h = hyphen; 1434 } 1435 while (!!(n = el.childNodes[i++])) { 1436 if (n.nodeType === 3) { 1437 n.data = n.data.replace(new RegExp(h, 'g'), ''); 1438 n.data = n.data.replace(new RegExp(zeroWidthSpace, 'g'), ''); 1439 } else if (n.nodeType === 1) { 1440 removeHyphenationFromElement(n); 1441 } 1442 } 1443 }, 1444 1445 /** 1446 * @name Hyphenator-hyphenateDocument 1447 * @description 1448 * Calls hyphenateElement() for all members of elements. This is done with a setTimout 1449 * to prevent a "long running Script"-alert when hyphenating large pages. 1450 * Therefore a tricky bind()-function was necessary. 1451 * @public 1452 */ 1453 hyphenateDocument = function () { 1454 function bind(fun, arg) { 1455 return function () { 1456 return fun(arg); 1457 }; 1458 } 1459 var i = 0, el; 1460 while (!!(el = elements[i++])) { 1461 if (el.ownerDocument.location.href === contextWindow.location.href) { 1462 window.setTimeout(bind(hyphenateElement, el), 0); 1463 } 1464 } 1465 }, 1466 1467 /** 1468 * @name Hyphenator-removeHyphenationFromDocument 1469 * @description 1470 * Does what it says ;-) 1471 * @public 1472 */ 1473 removeHyphenationFromDocument = function () { 1474 var i = 0, el; 1475 while (!!(el = elements[i++])) { 1476 removeHyphenationFromElement(el); 1477 } 1478 state = 4; 1479 }, 1480 1481 /** 1482 * @name Hyphenator-registerOnCopy 1483 * @description 1484 * Huge work-around for browser-inconsistency when it comes to 1485 * copying of hyphenated text. 1486 * The idea behind this code has been provided by http://github.com/aristus/sweet-justice 1487 * sweet-justice is under BSD-License 1488 * @private 1489 */ 1490 registerOnCopy = function () { 1491 var body = contextWindow.document.getElementsByTagName('body')[0], 1492 shadow, 1493 selection, 1494 range, 1495 rangeShadow, 1496 restore, 1497 oncopyHandler = function (e) { 1498 e = e || window.event; 1499 var target = e.target || e.srcElement, 1500 currDoc = target.ownerDocument, 1501 body = currDoc.getElementsByTagName('body')[0], 1502 targetWindow = 'defaultView' in currDoc ? currDoc.defaultView : currDoc.parentWindow; 1503 if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) { 1504 //Safari needs this 1505 return; 1506 } 1507 //create a hidden shadow element 1508 shadow = currDoc.createElement('div'); 1509 shadow.style.overflow = 'hidden'; 1510 shadow.style.position = 'absolute'; 1511 shadow.style.top = '-5000px'; 1512 shadow.style.height = '1px'; 1513 body.appendChild(shadow); 1514 if (window.getSelection) { 1515 //FF3, Webkit 1516 selection = targetWindow.getSelection(); 1517 range = selection.getRangeAt(0); 1518 shadow.appendChild(range.cloneContents()); 1519 removeHyphenationFromElement(shadow); 1520 selection.selectAllChildren(shadow); 1521 restore = function () { 1522 shadow.parentNode.removeChild(shadow); 1523 if (targetWindow.getSelection().setBaseAndExtent) { 1524 selection.setBaseAndExtent( 1525 range.startContainer, 1526 range.startOffset, 1527 range.endContainer, 1528 range.endOffset 1529 ); 1530 } 1531 }; 1532 } else { 1533 // IE 1534 selection = targetWindow.document.selection; 1535 range = selection.createRange(); 1536 shadow.innerHTML = range.htmlText; 1537 removeHyphenationFromElement(shadow); 1538 rangeShadow = body.createTextRange(); 1539 rangeShadow.moveToElementText(shadow); 1540 rangeShadow.select(); 1541 restore = function () { 1542 shadow.parentNode.removeChild(shadow); 1543 if (range.text !== "") { 1544 range.select(); 1545 } 1546 }; 1547 } 1548 window.setTimeout(restore, 0); 1549 }; 1550 if (!body) { 1551 return; 1552 } 1553 if (window.addEventListener) { 1554 body.addEventListener("copy", oncopyHandler, false); 1555 } else { 1556 body.attachEvent("oncopy", oncopyHandler); 1557 } 1558 }; 1559 1560 return { 1561 1562 /** 1563 * @name Hyphenator.version 1564 * @memberOf Hyphenator 1565 * @description 1566 * String containing the actual version of Hyphenator.js 1567 * [major release].[minor releas].[bugfix release] 1568 * major release: new API, new Features, big changes 1569 * minor release: new languages, improvements 1570 * @public 1571 */ 1572 version: '3.1.0', 1573 1574 /** 1575 * @name Hyphenator.languages 1576 * @memberOf Hyphenator 1577 * @description 1578 * Objects that holds key-value pairs, where key is the language and the value is the 1579 * language-object loaded from (and set by) the pattern file. 1580 * The language object holds the following members: 1581 * <table> 1582 * <tr><th>key</th><th>desc></th></tr> 1583 * <tr><td>leftmin</td><td>The minimum of chars to remain on the old line</td></tr> 1584 * <tr><td>rightmin</td><td>The minimum of chars to go on the new line</td></tr> 1585 * <tr><td>shortestPattern</td><td>The shortes pattern (numbers don't count!)</td></tr> 1586 * <tr><td>longestPattern</td><td>The longest pattern (numbers don't count!)</td></tr> 1587 * <tr><td>specialChars</td><td>Non-ASCII chars in the alphabet.</td></tr> 1588 * <tr><td>patterns</td><td>the patterns</td></tr> 1589 * </table> 1590 * And optionally (or after prepareLanguagesObj() has been called): 1591 * <table> 1592 * <tr><td>exceptions</td><td>Excpetions for the secified language</td></tr> 1593 * </table> 1594 * @public 1595 */ 1596 languages: {}, 1597 1598 1599 /** 1600 * @name Hyphenator.config 1601 * @description 1602 * Config function that takes an object as an argument. The object contains key-value-pairs 1603 * containig Hyphenator-settings. This is a shortcut for calling Hyphenator.set...-Methods. 1604 * @param {Object} obj <table> 1605 * <tr><th>key</th><th>values</th><th>default</th></tr> 1606 * <tr><td>classname</td><td>string</td><td>'hyphenate'</td></tr> 1607 * <tr><td>donthyphenateclassname</td><td>string</td><td>''</td></tr> 1608 * <tr><td>minwordlength</td><td>integer</td><td>6</td></tr> 1609 * <tr><td>hyphenchar</td><td>string</td><td>'&shy;'</td></tr> 1610 * <tr><td>urlhyphenchar</td><td>string</td><td>'zero with space'</td></tr> 1611 * <tr><td>togglebox</td><td>function</td><td>see code</td></tr> 1612 * <tr><td>displaytogglebox</td><td>boolean</td><td>false</td></tr> 1613 * <tr><td>remoteloading</td><td>boolean</td><td>true</td></tr> 1614 * <tr><td>enablecache</td><td>boolean</td><td>true</td></tr> 1615 * <tr><td>enablereducedpatternset</td><td>boolean</td><td>false</td></tr> 1616 * <tr><td>onhyphenationdonecallback</td><td>function</td><td>empty function</td></tr> 1617 * <tr><td>onerrorhandler</td><td>function</td><td>alert(onError)</td></tr> 1618 * <tr><td>intermediatestate</td><td>string</td><td>'hidden'</td></tr> 1619 * <tr><td>selectorfunction</td><td>function</td><td>[…]</td></tr> 1620 * <tr><td>safecopy</td><td>boolean</td><td>true</td></tr> 1621 * <tr><td>doframes</td><td>boolean</td><td>false</td></tr> 1622 * <tr><td>storagetype</td><td>string</td><td>'none'</td></tr> 1623 * </table> 1624 * @public 1625 * @example <script src = "Hyphenator.js" type = "text/javascript"></script> 1626 * <script type = "text/javascript"> 1627 * Hyphenator.config({'minwordlength':4,'hyphenchar':'|'}); 1628 * Hyphenator.run(); 1629 * </script> 1630 */ 1631 config: function (obj) { 1632 var assert = function (name, type) { 1633 if (typeof obj[name] === type) { 1634 return true; 1635 } else { 1636 onError(new Error('Config onError: ' + name + ' must be of type ' + type)); 1637 return false; 1638 } 1639 }, 1640 key; 1641 for (key in obj) { 1642 if (obj.hasOwnProperty(key)) { 1643 switch (key) { 1644 case 'classname': 1645 if (assert('classname', 'string')) { 1646 hyphenateClass = obj[key]; 1647 } 1648 break; 1649 case 'donthyphenateclassname': 1650 if (assert('donthyphenateclassname', 'string')) { 1651 dontHyphenateClass = obj[key]; 1652 } 1653 break; 1654 case 'minwordlength': 1655 if (assert('minwordlength', 'number')) { 1656 min = obj[key]; 1657 } 1658 break; 1659 case 'hyphenchar': 1660 if (assert('hyphenchar', 'string')) { 1661 if (obj.hyphenchar === '­') { 1662 obj.hyphenchar = String.fromCharCode(173); 1663 } 1664 hyphen = obj[key]; 1665 } 1666 break; 1667 case 'urlhyphenchar': 1668 if (obj.hasOwnProperty('urlhyphenchar')) { 1669 if (assert('urlhyphenchar', 'string')) { 1670 urlhyphen = obj[key]; 1671 } 1672 } 1673 break; 1674 case 'togglebox': 1675 if (assert('togglebox', 'function')) { 1676 toggleBox = obj[key]; 1677 } 1678 break; 1679 case 'displaytogglebox': 1680 if (assert('displaytogglebox', 'boolean')) { 1681 displayToggleBox = obj[key]; 1682 } 1683 break; 1684 case 'remoteloading': 1685 if (assert('remoteloading', 'boolean')) { 1686 enableRemoteLoading = obj[key]; 1687 } 1688 break; 1689 case 'enablecache': 1690 if (assert('enablecache', 'boolean')) { 1691 enableCache = obj[key]; 1692 } 1693 break; 1694 case 'enablereducedpatternset': 1695 if (assert('enablereducedpatternset', 'boolean')) { 1696 enableReducedPatternSet = obj[key]; 1697 } 1698 break; 1699 case 'onhyphenationdonecallback': 1700 if (assert('onhyphenationdonecallback', 'function')) { 1701 onHyphenationDone = obj[key]; 1702 } 1703 break; 1704 case 'onerrorhandler': 1705 if (assert('onerrorhandler', 'function')) { 1706 onError = obj[key]; 1707 } 1708 break; 1709 case 'intermediatestate': 1710 if (assert('intermediatestate', 'string')) { 1711 intermediateState = obj[key]; 1712 } 1713 break; 1714 case 'selectorfunction': 1715 if (assert('selectorfunction', 'function')) { 1716 selectorFunction = obj[key]; 1717 } 1718 break; 1719 case 'safecopy': 1720 if (assert('safecopy', 'boolean')) { 1721 safeCopy = obj[key]; 1722 } 1723 break; 1724 case 'doframes': 1725 if (assert('doframes', 'boolean')) { 1726 doFrames = obj[key]; 1727 } 1728 break; 1729 case 'storagetype': 1730 if (assert('storagetype', 'string')) { 1731 storageType = obj[key]; 1732 } 1733 break; 1734 case 'orphancontrol': 1735 if (assert('orphancontrol', 'number')) { 1736 orphanControl = obj[key]; 1737 } 1738 break; 1739 default: 1740 onError(new Error('Hyphenator.config: property ' + key + ' not known.')); 1741 } 1742 } 1743 } 1744 }, 1745 1746 /** 1747 * @name Hyphenator.run 1748 * @description 1749 * Bootstrap function that starts all hyphenation processes when called. 1750 * @public 1751 * @example <script src = "Hyphenator.js" type = "text/javascript"></script> 1752 * <script type = "text/javascript"> 1753 * Hyphenator.run(); 1754 * </script> 1755 */ 1756 run: function () { 1757 documentCount = 0; 1758 var process = function () { 1759 try { 1760 if (contextWindow.document.getElementsByTagName('frameset').length > 0) { 1761 return; //we are in a frameset 1762 } 1763 documentCount++; 1764 autoSetMainLanguage(undefined); 1765 gatherDocumentInfos(); 1766 prepare(hyphenateDocument); 1767 if (displayToggleBox) { 1768 toggleBox(true); 1769 } 1770 if (safeCopy) { 1771 registerOnCopy(); 1772 } 1773 } catch (e) { 1774 onError(e); 1775 } 1776 }, i, haveAccess, fl = window.frames.length; 1777 try { 1778 if (storageType !== 'none' && 1779 typeof(window.localStorage) !== 'undefined' && 1780 typeof(window.sessionStorage) !== 'undefined' && 1781 typeof(window.JSON.stringify) !== 'undefined' && 1782 typeof(window.JSON.parse) !== 'undefined') { 1783 switch (storageType) { 1784 case 'session': 1785 storage = window.sessionStorage; 1786 break; 1787 case 'local': 1788 storage = window.localStorage; 1789 break; 1790 default: 1791 storage = undefined; 1792 break; 1793 } 1794 } 1795 } catch (f) { 1796 //FF throws an error if DOM.storage.enabled is set to false 1797 } 1798 if (!documentLoaded && !isBookmarklet) { 1799 runOnContentLoaded(window, process); 1800 } 1801 if (isBookmarklet || documentLoaded) { 1802 if (doFrames && fl > 0) { 1803 for (i = 0; i < fl; i++) { 1804 haveAccess = undefined; 1805 //try catch isn't enough for webkit 1806 try { 1807 //opera throws only on document.toString-access 1808 haveAccess = window.frames[i].document.toString(); 1809 } catch (e) { 1810 haveAccess = undefined; 1811 } 1812 if (!!haveAccess) { 1813 contextWindow = window.frames[i]; 1814 process(); 1815 } 1816 } 1817 } 1818 contextWindow = window; 1819 process(); 1820 } 1821 }, 1822 1823 /** 1824 * @name Hyphenator.addExceptions 1825 * @description 1826 * Adds the exceptions from the string to the appropriate language in the 1827 * {@link Hyphenator-languages}-object 1828 * @param {string} lang The language 1829 * @param {string} words A comma separated string of hyphenated words WITH spaces. 1830 * @public 1831 * @example <script src = "Hyphenator.js" type = "text/javascript"></script> 1832 * <script type = "text/javascript"> 1833 * Hyphenator.addExceptions('de','ziem-lich, Wach-stube'); 1834 * Hyphenator.run(); 1835 * </script> 1836 */ 1837 addExceptions: function (lang, words) { 1838 if (lang === '') { 1839 lang = 'global'; 1840 } 1841 if (exceptions.hasOwnProperty(lang)) { 1842 exceptions[lang] += ", " + words; 1843 } else { 1844 exceptions[lang] = words; 1845 } 1846 }, 1847 1848 /** 1849 * @name Hyphenator.hyphenate 1850 * @public 1851 * @description 1852 * Hyphenates the target. The language patterns must be loaded. 1853 * If the target is a string, the hyphenated string is returned, 1854 * if it's an object, the values are hyphenated directly. 1855 * @param {string|Object} target the target to be hyphenated 1856 * @param {string} lang the language of the target 1857 * @returns string 1858 * @example <script src = "Hyphenator.js" type = "text/javascript"></script> 1859 * <script src = "patterns/en.js" type = "text/javascript"></script> 1860 * <script type = "text/javascript"> 1861 * var t = Hyphenator.hyphenate('Hyphenation', 'en'); //Hy|phen|ation 1862 * </script> 1863 */ 1864 hyphenate: function (target, lang) { 1865 var hyphenate, n, i; 1866 if (Hyphenator.languages.hasOwnProperty(lang)) { 1867 if (!Hyphenator.languages[lang].prepared) { 1868 prepareLanguagesObj(lang); 1869 } 1870 hyphenate = function (word) { 1871 if (urlOrMailRE.test(word)) { 1872 return hyphenateURL(word); 1873 } else { 1874 return hyphenateWord(lang, word); 1875 } 1876 }; 1877 if (typeof target === 'string' || target.constructor === String) { 1878 return target.replace(Hyphenator.languages[lang].genRegExp, hyphenate); 1879 } else if (typeof target === 'object') { 1880 i = 0; 1881 while (!!(n = target.childNodes[i++])) { 1882 if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate! 1883 n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate); 1884 } else if (n.nodeType === 1) { 1885 if (n.lang !== '') { 1886 Hyphenator.hyphenate(n, n.lang); 1887 } else { 1888 Hyphenator.hyphenate(n, lang); 1889 } 1890 } 1891 } 1892 } 1893 } else { 1894 onError(new Error('Language "' + lang + '" is not loaded.')); 1895 } 1896 }, 1897 1898 /** 1899 * @name Hyphenator.getRedPatternSet 1900 * @description 1901 * Returns {@link Hyphenator-isBookmarklet}. 1902 * @param {string} lang the language patterns are stored for 1903 * @returns object {'patk': pat} 1904 * @public 1905 */ 1906 getRedPatternSet: function (lang) { 1907 return Hyphenator.languages[lang].redPatSet; 1908 }, 1909 1910 /** 1911 * @name Hyphenator.isBookmarklet 1912 * @description 1913 * Returns {@link Hyphenator-isBookmarklet}. 1914 * @returns boolean 1915 * @public 1916 */ 1917 isBookmarklet: function () { 1918 return isBookmarklet; 1919 }, 1920 1921 getConfigFromURI: function () { 1922 var loc = null, re = {}, jsArray = document.getElementsByTagName('script'), i, j, l, s, gp, option; 1923 for (i = 0, l = jsArray.length; i < l; i++) { 1924 if (!!jsArray[i].getAttribute('src')) { 1925 loc = jsArray[i].getAttribute('src'); 1926 } 1927 if (!loc) { 1928 continue; 1929 } else { 1930 s = loc.indexOf('Hyphenator.js?'); 1931 if (s === -1) { 1932 continue; 1933 } 1934 gp = loc.substring(s + 14).split('&'); 1935 for (j = 0; j < gp.length; j++) { 1936 option = gp[j].split('='); 1937 if (option[0] === 'bm') { 1938 continue; 1939 } 1940 if (option[1] === 'true') { 1941 re[option[0]] = true; 1942 continue; 1943 } 1944 if (option[1] === 'false') { 1945 re[option[0]] = false; 1946 continue; 1947 } 1948 if (isFinite(option[1])) { 1949 re[option[0]] = parseInt(option[1], 10); 1950 continue; 1951 } 1952 if (option[0] === 'onhyphenationdonecallback') { 1953 re[option[0]] = new Function('', option[1]); 1954 continue; 1955 } 1956 re[option[0]] = option[1]; 1957 } 1958 break; 1959 } 1960 } 1961 return re; 1962 }, 1963 1964 /** 1965 * @name Hyphenator.toggleHyphenation 1966 * @description 1967 * Checks the current state of the ToggleBox and removes or does hyphenation. 1968 * @public 1969 */ 1970 toggleHyphenation: function () { 1971 switch (state) { 1972 case 3: 1973 removeHyphenationFromDocument(); 1974 toggleBox(false); 1975 break; 1976 case 4: 1977 hyphenateDocument(); 1978 toggleBox(true); 1979 break; 1980 } 1981 } 1982 }; 1983}(window)); 1984 1985//Export properties/methods (for google closure compiler) 1986Hyphenator['languages'] = Hyphenator.languages; 1987Hyphenator['config'] = Hyphenator.config; 1988Hyphenator['run'] = Hyphenator.run; 1989Hyphenator['addExceptions'] = Hyphenator.addExceptions; 1990Hyphenator['hyphenate'] = Hyphenator.hyphenate; 1991Hyphenator['getRedPatternSet'] = Hyphenator.getRedPatternSet; 1992Hyphenator['isBookmarklet'] = Hyphenator.isBookmarklet; 1993Hyphenator['getConfigFromURI'] = Hyphenator.getConfigFromURI; 1994Hyphenator['toggleHyphenation'] = Hyphenator.toggleHyphenation; 1995window['Hyphenator'] = Hyphenator; 1996 1997if (Hyphenator.isBookmarklet()) { 1998 Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', doframes: true}); 1999 Hyphenator.config(Hyphenator.getConfigFromURI()); 2000 Hyphenator.run(); 2001}