PageSourceSearch

https://www.eschbronn.de/tools/js/Hyphenator.js

js eschbronn.de collected 2026-10-02 20:47:26 UTC 135,203 bytes, 3,397 lines download raw bytes

1/** @license Hyphenator 5.2.0(devel) - client side hyphenation for webbrowsers
2 *  Copyright (C) 2015  Mathias Nater, Zürich (mathiasnater at gmail dot com)
3 *  https://github.com/mnater/Hyphenator
4 *
5 *  Released under the MIT license
6 *  http://mnater.github.io/Hyphenator/LICENSE.txt
7 */
8
9/*
10 * Comments are jsdoc3 formatted. See http://usejsdoc.org
11 * Use mergeAndPack.html to get rid of the comments and to reduce the file size of this script!
12 */
13
14/* The following comment is for JSLint: */
15/*jslint browser: true for: false*/
16/*global Hyphenator window*/
17
18/**
19 * @desc Provides all functionality to do hyphenation, except the patterns that are loaded externally
20 * @global
21 * @namespace Hyphenator
22 * @author Mathias Nater, <[email protected]>
23 * @version 5.2.0(devel)
24 * @example
25 * &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
26 * &lt;script type = "text/javascript"&gt;
27 *   Hyphenator.run();
28 * &lt;/script&gt;
29 */
30var Hyphenator;
31Hyphenator = (function (window) {
32    'use strict';
33
34    /**
35     * @member Hyphenator~contextWindow
36     * @access private
37     * @desc
38     * contextWindow stores the window for the actual document to be hyphenated.
39     * If there are frames this will change.
40     * So use contextWindow instead of window!
41     */
42    var contextWindow = window;
43
44
45    /**
46     * @member {Object.<string, Hyphenator~supportedLangs~supportedLanguage>} Hyphenator~supportedLangs
47     * @desc
48     * A generated key-value object that stores supported languages and meta data.
49     * The key is the {@link http://tools.ietf.org/rfc/bcp/bcp47.txt bcp47} code of the language and the value
50     * is an object of type {@link Hyphenator~supportedLangs~supportedLanguage}
51     * @namespace Hyphenator~supportedLangs
52     * @access private
53     * //Check if language lang is supported:
54     * if (supportedLangs.hasOwnProperty(lang))
55     */
56    var supportedLangs = (function () {
57        /**
58         * @typedef {Object} Hyphenator~supportedLangs~supportedLanguage
59         * @property {string} file - The name of the pattern file
60         * @property {number} script - The script type of the language (e.g. 'latin' for english), this type is abbreviated by an id
61         * @property {string} prompt - The sentence prompted to the user, if Hyphenator.js doesn't find a language hint
62         */
63
64        /**
65         * @lends Hyphenator~supportedLangs
66         */
67        var r = {},
68            /**
69             * @method Hyphenator~supportedLangs~o
70             * @desc
71             * Sets a value of Hyphenator~supportedLangs
72             * @access protected
73             * @param {string} code The {@link http://tools.ietf.org/rfc/bcp/bcp47.txt bcp47} code of the language
74             * @param {string} file The name of the pattern file
75             * @param {Number} script A shortcut for a specific script: latin:0, cyrillic: 1, arabic: 2, armenian:3, bengali: 4, devangari: 5, greek: 6
76             * gujarati: 7, kannada: 8, lao: 9, malayalam: 10, oriya: 11, persian: 12, punjabi: 13, tamil: 14, telugu: 15
77             * @param {string} prompt The sentence prompted to the user, if Hyphenator.js doesn't find a language hint
78             */
79            o = function (code, file, script, prompt) {
80                r[code] = {'file': file, 'script': script, 'prompt': prompt};
81            };
82
83        o('be', 'be.js', 1, 'Мова гэтага сайта не можа быць вызначаны аўтаматычна. Калі ласка пакажыце мову:');
84        o('ca', 'ca.js', 0, '');
85        o('cs', 'cs.js', 0, 'Jazyk této internetové stránky nebyl automaticky rozpoznán. Určete prosím její jazyk:');
86        o('da', 'da.js', 0, 'Denne websides sprog kunne ikke bestemmes. Angiv venligst sprog:');
87        o('bn', 'bn.js', 4, '');
88        o('de', 'de.js', 0, 'Die Sprache dieser Webseite konnte nicht automatisch bestimmt werden. Bitte Sprache angeben:');
89        o('el', 'el-monoton.js', 6, '');
90        o('el-monoton', 'el-monoton.js', 6, '');
91        o('el-polyton', 'el-polyton.js', 6, '');
92        o('en', 'en-us.js', 0, 'The language of this website could not be determined automatically. Please indicate the main language:');
93        o('en-gb', 'en-gb.js', 0, 'The language of this website could not be determined automatically. Please indicate the main language:');
94        o('en-us', 'en-us.js', 0, 'The language of this website could not be determined automatically. Please indicate the main language:');
95        o('eo', 'eo.js', 0, 'La lingvo de ĉi tiu retpaĝo ne rekoneblas aŭtomate. Bonvolu indiki ĝian ĉeflingvon:');
96        o('es', 'es.js', 0, 'El idioma del sitio no pudo determinarse autom%E1ticamente. Por favor, indique el idioma principal:');
97        o('et', 'et.js', 0, 'Veebilehe keele tuvastamine ebaõnnestus, palun valige kasutatud keel:');
98        o('fi', 'fi.js', 0, 'Sivun kielt%E4 ei tunnistettu automaattisesti. M%E4%E4rit%E4 sivun p%E4%E4kieli:');
99        o('fr', 'fr.js', 0, 'La langue de ce site n%u2019a pas pu %EAtre d%E9termin%E9e automatiquement. Veuillez indiquer une langue, s.v.p.%A0:');
100        o('grc', 'grc.js', 6, '');
101        o('gu', 'gu.js', 7, '');
102        o('hi', 'hi.js', 5, '');
103        o('hu', 'hu.js', 0, 'A weboldal nyelvét nem sikerült automatikusan megállapítani. Kérem adja meg a nyelvet:');
104        o('hy', 'hy.js', 3, 'Չհաջողվեց հայտնաբերել Õ¡Õ
104µÕ½ կայքի լեզուն։ Խնդրում ենք նշեք Õ°Õ«Õ´Õ¶Õ¡Õ¯Õ¡Õ¶ լեզուն՝');
105        o('it', 'it.js', 0, 'Lingua del sito sconosciuta. Indicare una lingua, per favore:');
106        o('kn', 'kn.js', 8, 'ಜಾಲ ತಾಣದ ಭಾಷೆಯನ್ನು ನಿರ್ಧರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಮುಖ್ಯ ಭಾಷೆಯನ್ನು ಸೂಚಿಸಿ:');
107        o('la', 'la.js', 0, '');
108        o('lt', 'lt.js', 0, 'Nepavyko automatiškai nustatyti šios svetainės kalbos. Prašome įvesti kalbą:');
109        o('lv', 'lv.js', 0, 'Šīs lapas valodu nevarēja noteikt automātiski. Lūdzu norādiet pamata valodu:');
110        o('ml', 'ml.js', 10, 'ഈ വെ%u0D2C%u0D4D%u200Cസൈറ്റിന്റെ ഭാഷ കണ്ടുപിടിയ്ക്കാ%u0D28%u0D4D%u200D കഴിഞ്ഞില്ല. ഭാഷ ഏതാണെന്നു തിരഞ്ഞെടുക്കുക:');
111        o('nb', 'nb-no.js', 0, 'Nettstedets språk kunne ikke finnes automatisk. Vennligst oppgi språk:');
112        o('no', 'nb-no.js', 0, 'Nettstedets språk kunne ikke finnes automatisk. Vennligst oppgi språk:');
113        o('nb-no', 'nb-no.js', 0, 'Nettstedets språk kunne ikke finnes automatisk. Vennligst oppgi språk:');
114        o('nl', 'nl.js', 0, 'De taal van deze website kan niet automatisch worden bepaald. Geef de hoofdtaal op:');
115        o('or', 'or.js', 11, '');
116        o('pa', 'pa.js', 13, '');
117        o('pl', 'pl.js', 0, 'Języka tej strony nie można ustalić automatycznie. Proszę wskazać język:');
118        o('pt', 'pt.js', 0, 'A língua deste site não pôde ser determinada automaticamente. Por favor indique a língua principal:');
119        o('ru', 'ru.js', 1, 'Язык этого сайта не может быть определен автоматически. Пожалуйста укажите язык:');
120        o('sk', 'sk.js', 0, '');
121        o('sl', 'sl.js', 0, 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:');
122        o('sr-cyrl', 'sr-cyrl.js', 1, 'Језик овог сајта није детектован аутоматски. Молим вас наведите језик:');
123        o('sr-latn', 'sr-latn.js', 0, 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:');
124        o('sv', 'sv.js', 0, 'Spr%E5ket p%E5 den h%E4r webbplatsen kunde inte avg%F6ras automatiskt. V%E4nligen ange:');
125        o('ta', 'ta.js', 14, '');
126        o('te', 'te.js', 15, '');
127        o('tr', 'tr.js', 0, 'Bu web sitesinin dili otomatik olarak tespit edilememiştir. Lütfen dökümanın dilini seçiniz%A0:');
128        o('uk', 'uk.js', 1, 'Мова цього веб-сайту не може бути визначена автоматично. Будь ласка, вкажіть головну мову:');
129        o('ro', 'ro.js', 0, 'Limba acestui sit nu a putut fi determinată automat. Alege limba principală:');
130
131        return r;
132    }());
133
134
135    /**
136     * @member {string} Hyphenator~basePath
137     * @desc
138     * A string storing the basepath from where Hyphenator.js was loaded.
139     * This is used to load the pattern files.
140     * The basepath is determined dynamically by searching all script-tags for Hyphenator.js
141     * If the path cannot be determined {@link http://mnater.github.io/Hyphenator/} is used as fallback.
142     * @access private
143     * @see {@link Hyphenator~loadPatterns}
144     */
145    var basePath = (function () {
146        var s = contextWindow.document.getElementsByTagName('script'), i = 0, p, src, t = s[i], r = '';
147        while (!!t) {
148            if (!!t.src) {
149                src = t.src;
150                p = src.indexOf('Hyphenator.js');
151                if (p !== -1) {
152                    r = src.substring(0, p);
153                }
154            }
155            i += 1;
156            t = s[i];
157        }
158        return !!r
159            ? r
160            : '//mnater.github.io/Hyphenator/';
161    }());
162
163    /**
164     * @member {boolean} Hyphenator~isLocal
165     * @access private
166     * @desc
167     * isLocal is true, if Hyphenator is loaded from the same domain, as the webpage, but false, if
168     * it's loaded from an external source (i.e. directly from github)
169     */
170    var isLocal = (function () {
171        var re = false;
172        if (window.location.href.indexOf(basePath) !== -1) {
173            re = true;
174        }
175        return re;
176    }());
177
178    /**
179     * @member {boolean} Hyphenator~documentLoaded
180     * @access private
181     * @desc
182     * documentLoaded is true, when the DOM has been loaded. This is set by {@link Hyphenator~runWhenLoaded}
183     */
184    var documentLoaded = false;
185
186    /**
187     * @member {boolean} Hyphenator~persistentConfig
188     * @access private
189     * @desc
190     * if persistentConfig is set to true (defaults to false), config options and the state of the
191     * toggleBox are stored in DOM-storage (according to the storage-setting). So they haven't to be
192     * set for each page.
193     * @default false
194     * @see {@link Hyphenator.config}
195     */
196    var persistentConfig = false;
197
198    /**
199     * @member {boolean} Hyphenator~doFrames
200     * @access private
201     * @desc
202     * switch to control if frames/iframes should be hyphenated, too.
203     * defaults to false (frames are a bag of hurt!)
204     * @default false
205     * @see {@link Hyphenator.config}
206     */
207    var doFrames = false;
208
209    /**
210     * @member {Object.<string,boolean>} Hyphenator~dontHyphenate
211     * @desc
212     * A key-value object containing all html-tags whose content should not be hyphenated
213     * @access private
214     */
215    var dontHyphenate = {'video': true, 'audio': true, '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, 'math': true, 'svg': true, 'style': true};
216
217    /**
218     * @member {boolean} Hyphenator~enableCache
219     * @desc
220     * A variable to set if caching is enabled or not
221     * @default true
222     * @access private
223     * @see {@link Hyphenator.config}
224     */
225    var enableCache = true;
226
227    /**
228     * @member {string} Hyphenator~storageType
229     * @desc
230     * A variable to define what html5-DOM-Storage-Method is used ('none', 'local' or 'session')
231     * @default 'local'
232     * @access private
233     * @see {@link Hyphenator.config}
234     */
235    var storageType = 'local';
236
237    /**
238     * @member {Object|undefined} Hyphenator~storage
239     * @desc
240     * An alias to the storage defined in storageType. This is set by {@link Hyphenator~createStorage}.
241     * Set by {@link Hyphenator.run}
242     * @default null
243     * @access private
244     * @see {@link Hyphenator~createStorage}
245     */
246    var storage;
247
248    /**
249     * @member {boolean} Hyphenator~enableReducedPatternSet
250     * @desc
251     * A variable to set if storing the used patterns is set
252     * @default false
253     * @access private
254     * @see {@link Hyphenator.config}
255     * @see {@link Hyphenator.getRedPatternSet}
256     */
257    var enableReducedPatternSet = false;
258
259    /**
260     * @member {boolean} Hyphenator~enableRemoteLoading
261     * @desc
262     * A variable to set if pattern files should be loaded remotely or not
263     * @default true
264     * @access private
265     * @see {@link Hyphenator.config}
266     */
267    var enableRemoteLoading = true;
268
269    /**
270     * @member {boolean} Hyphenator~displayToggleBox
271     * @desc
272     * A variable to set if the togglebox should be displayed or not
273     * @default false
274     * @access private
275     * @see {@link Hyphenator.config}
276     */
277    var displayToggleBox = false;
278
279    /**
280     * @method Hyphenator~onError
281     * @desc
282     * A function that can be called upon an error.
283     * @see {@link Hyphenator.config}
284     * @access private
285     */
286    var onError = function (e) {
287        window.alert("Hyphenator.js says:\n\nAn Error occurred:\n" + e.message);
288    };
289
290    /**
291     * @method Hyphenator~onWarning
292     * @desc
293     * A function that can be called upon a warning.
294     * @see {@link Hyphenator.config}
295     * @access private
296     */
297    var onWarning = function (e) {
298        window.console.log(e.message);
299    };
300
301    /**
302     * @method Hyphenator~createElem
303     * @desc
304     * A function alias to document.createElementNS or document.createElement
305     * @access private
306     */
307    function createElem(tagname, context) {
308        context = context || contextWindow;
309        var el;
310        if (window.document.createElementNS) {
311            el = context.document.createElementNS('http://www.w3.org/1999/xhtml', tagname);
312        } else if (window.document.createElement) {
313            el = context.document.createElement(tagname);
314        }
315        return el;
316    }
317    /**
318     * @method Hyphenator~forEachKey
319     * @desc
320     * Calls the function f on every property of o
321     * @access private
322     */
323    function forEachKey(o, f) {
324        var a, k;
325        if (Object.hasOwnProperty("keys")) {
326            a = Object.keys(o);
327            a.forEach(function (v) {
328                f(v);
329            });
330        } else {
331            for (k in o) {
332                if (o.hasOwnProperty(k)) {
333                    f(k);
334                }
335            }
336        }
337    }
338
339    /**
340     * @member {boolean} Hyphenator~css3
341     * @desc
342     * A variable to set if css3 hyphenation should be used
343     * @default false
344     * @access private
345     * @see {@link Hyphenator.config}
346     */
347    var css3 = false;
348
349    /**
350     * @method Hyphenator~css3_gethsupport
351     * @desc
352     * This function returns a {@link Hyphenator~css3_hsupport} object for the current UA
353     * @type function
354     * @access private
355     * @see Hyphenator~css3_h9n
356     */
357    function css3_gethsupport() {
358        var support = false,
359            supportedBrowserLangs = {},
360            property = '',
361            checkLangSupport,
362            createLangSupportChecker = function (prefix) {
363                var testStrings = [
364                        //latin: 0
365                        'aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz',
366                        //cyrillic: 1
367                        'абвгдеёжзийклмнопрстуфхцчшщъыьэюя',
368                        //arabic: 2
369                        'أبتثجحخدذرزسشصضطظعغفقكلمنهوي',
370                        //armenian: 3
371                        'աբգդեզէըթժիլխծկհձղճմյնշոչպջռսվտրցւփքօֆ',
372                        //bengali: 4
373                        'ঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣ',
374                        //devangari: 5
375                        'ँंःअआइईउऊऋऌएऐओऔकखगघङचछजझञटठडढणतथदधनपफबभमयरलळवशषसहऽािीुूृॄेैोौ्॒॑ॠॡॢॣ',
376                        //greek: 6
377                        'αβγδεζηθικλμνξοπρσςτυφχψω',
378                        //gujarati: 7
379                        'બહઅઆઇઈઉઊઋૠએઐઓઔાિીુૂૃૄૢૣેૈોૌકખગઘઙચછજઝઞટઠડઢણતથદધનપફસભમયરલળવશષ',
380                        //kannada: 8
381                        'ಂಃಅಆಇಈಉಊಋಌಎಏಐಒಓಔಕಖಗಘಙಚಛಜಝಞಟಠಡಢಣತಥದಧನಪಫಬಭಮಯರಱಲಳವಶಷಸಹಽಾಿೀುೂೃೄೆೇೈೊೋೌ್ೕೖೞೠೡ',
382                        //lao: 9
383                        'ກຂຄງຈຊຍດຕຖທນບປຜຝພຟມຢຣລວສຫອຮະັາິີຶືຸູົຼເແໂໃໄ່້໊໋ໜໝ',
384                        //malayalam: 10
385                        'ംഃഅആഇഈഉഊഋഌഎഏഐഒഓഔകഖഗഘങചഛജഝഞടഠഡഢണതഥദധനപഫബഭമയരറലളഴവശഷസഹാിീുൂൃെേൈൊോൌ്ൗൠൡൺൻർൽൾൿ',
386                        //oriya: 11
387                        'ଁଂଃଅଆଇଈଉଊଋଌଏଐଓଔକଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଳଵଶଷସହାିୀୁୂୃେୈୋୌ୍ୗୠୡ',
388                        //persian: 12
389                        'أبتثجحخدذرزسشصضطظعغفقكلمنهوي',
390                        //punjabi: 13
391                        'ਁਂਃਅਆਇਈਉਊਏਐਓਔਕਖਗਘਙਚਛਜਝਞਟਠਡਢਣਤਥਦਧਨਪਫਬਭਮਯਰਲਲ਼ਵਸ਼ਸਹਾਿ
391ੀੁੂੇੈੋੌ੍ੰੱ',
392                        //tamil: 14
393                        'ஃஅஆஇஈஉஊஎஏஐஒஓஔகஙசஜஞடணதநனபமயரறலளழவஷஸஹாிீுூெேைொோௌ்ௗ',
394                        //telugu: 15
395                        'ఁంఃఅఆఇఈఉఊఋఌఎఏఐఒఓఔకఖగఘఙచఛజఝఞటఠడఢణతథదధనపఫబభమయరఱలళవశషసహాిీుూృౄెేైొోౌ్ౕౖౠౡ'
396                    ],
397                    f = function (lang) {
398                        var shadow,
399                            computedHeight,
400                            bdy,
401                            r = false;
402
403                        //check if lang has already been tested
404                        if (supportedBrowserLangs.hasOwnProperty(lang)) {
405                            r = supportedBrowserLangs[lang];
406                        } else if (supportedLangs.hasOwnProperty(lang)) {
407                            //create and append shadow-test-element
408                            bdy = window.document.getElementsByTagName('body')[0];
409                            shadow = createElem('div', window);
410                            shadow.id = 'Hyphenator_LanguageChecker';
411                            shadow.style.width = '5em';
412                            shadow.style.padding = '0';
413                            shadow.style.border = 'none';
414                            shadow.style[prefix] = 'auto';
415                            shadow.style.hyphens = 'auto';
416                            shadow.style.fontSize = '12px';
417                            shadow.style.lineHeight = '12px';
418                            shadow.style.wordWrap = 'normal';
419                            shadow.style.visibility = 'hidden';
420                            shadow.lang = lang;
421                            shadow.style['-webkit-locale'] = "'" + lang + "'";
422                            shadow.innerHTML = testStrings[supportedLangs[lang].script];
423                            bdy.appendChild(shadow);
424                            //measure its height
425                            computedHeight = shadow.offsetHeight;
426                            //remove shadow element
427                            bdy.removeChild(shadow);
428                            r = !!(computedHeight > 12);
429                            supportedBrowserLangs[lang] = r;
430                        } else {
431                            r = false;
432                        }
433                        return r;
434                    };
435                return f;
436            },
437            s;
438
439        if (window.getComputedStyle) {
440            s = window.getComputedStyle(window.document.getElementsByTagName('body')[0], null);
441            if (s.hyphens !== undefined) {
442                support = true;
443                property = 'hyphens';
444                checkLangSupport = createLangSupportChecker('hyphens');
445            } else if (s['-webkit-hyphens'] !== undefined) {
446                support = true;
447                property = '-webkit-hyphens';
448                checkLangSupport = createLangSupportChecker('-webkit-hyphens');
449            } else if (s.MozHyphens !== undefined) {
450                support = true;
451                property = '-moz-hyphens';
452                checkLangSupport = createLangSupportChecker('MozHyphens');
453            } else if (s['-ms-hyphens'] !== undefined) {
454                support = true;
455                property = '-ms-hyphens';
456                checkLangSupport = createLangSupportChecker('-ms-hyphens');
457            }
458        } //else we just return the initial values because ancient browsers don't support css3 anyway
459
460
461        return {
462            support: support,
463            property: property,
464            supportedBrowserLangs: supportedBrowserLangs,
465            checkLangSupport: checkLangSupport
466        };
467    }
468
469    /**
470     * @typedef {Object} Hyphenator~css3_hsupport
471     * @property {boolean} support - if css3-hyphenation is supported
472     * @property {string} property - the css property name to access hyphen-settings (e.g. -webkit-hyphens)
473     * @property {Object.<string, boolean>} supportedBrowserLangs - an object caching tested languages
474     * @property {function} checkLangSupport - a method that checks if the browser supports a requested language
475     */
476
477    /**
478     * @member {Hyphenator~css3_h9n} Hyphenator~css3_h9n
479     * @desc
480     * A generated object containing information for CSS3-hyphenation support
481     * This is set by {@link Hyphenator~css3_gethsupport}
482     * @default undefined
483     * @access private
484     * @see {@link Hyphenator~css3_gethsupport}
485     * @example
486     * //Check if browser supports a language
487     * css3_h9n.checkLangSupport(&lt;lang&gt;)
488     */
489    var css3_h9n;
490
491    /**
492     * @member {string} Hyphenator~hyphenateClass
493     * @desc
494     * A string containing the css-class-name for the hyphenate class
495     * @default 'hyphenate'
496     * @access private
497     * @example
498     * &lt;p class = "hyphenate"&gt;Text&lt;/p&gt;
499     * @see {@link Hyphenator.config}
500     */
501    var hyphenateClass = 'hyphenate';
502
503    /**
504     * @member {string} Hyphenator~urlHyphenateClass
505     * @desc
506     * A string containing the css-class-name for the urlhyphenate class
507     * @default 'urlhyphenate'
508     * @access private
509     * @example
510     * &lt;p class = "urlhyphenate"&gt;Text&lt;/p&gt;
511     * @see {@link Hyphenator.config}
512     */
513    var urlHyphenateClass = 'urlhyphenate';
514
515    /**
516     * @member {string} Hyphenator~classPrefix
517     * @desc
518     * A string containing a unique className prefix to be used
519     * whenever Hyphenator sets a CSS-class
520     * @access private
521     */
522    var classPrefix = 'Hyphenator' + Math.round(Math.random() * 1000);
523
524    /**
525     * @member {string} Hyphenator~hideClass
526     * @desc
527     * The name of the class that hides elements
528     * @access private
529     */
530    var hideClass = classPrefix + 'hide';
531
532    /**
533     * @member {RegExp} Hyphenator~hideClassRegExp
534     * @desc
535     * RegExp to remove hideClass from a list of classes
536     * @access private
537     */
538    var hideClassRegExp = new RegExp("\\s?\\b" + hideClass + "\\b", "g");
539
540    /**
541     * @member {string} Hyphenator~hideClass
542     * @desc
543     * The name of the class that unhides elements
544     * @access private
545     */
546    var unhideClass = classPrefix + 'unhide';
547
548    /**
549     * @member {RegExp} Hyphenator~hideClassRegExp
550     * @desc
551     * RegExp to remove unhideClass from a list of classes
552     * @access private
553     */
554    var unhideClassRegExp = new RegExp("\\s?\\b" + unhideClass + "\\b", "g");
555
556    /**
557     * @member {string} Hyphenator~css3hyphenateClass
558     * @desc
559     * The name of the class that hyphenates elements with css3
560     * @access private
561     */
562    var css3hyphenateClass = classPrefix + 'css3hyphenate';
563
564    /**
565     * @member {CSSEdit} Hyphenator~css3hyphenateClass
566     * @desc
567     * The var where CSSEdit class is stored
568     * @access private
569     */
570    var css3hyphenateClassHandle;
571
572    /**
573     * @member {string} Hyphenator~dontHyphenateClass
574     * @desc
575     * A string containing the css-class-name for elements that should not be hyphenated
576     * @default 'donthyphenate'
577     * @access private
578     * @example
579     * &lt;p class = "donthyphenate"&gt;Text&lt;/p&gt;
580     * @see {@link Hyphenator.config}
581     */
582    var dontHyphenateClass = 'donthyphenate';
583
584    /**
585     * @member {number} Hyphenator~min
586     * @desc
587     * A number wich indicates the minimal length of words to hyphenate.
588     * @default 6
589     * @access private
590     * @see {@link Hyphenator.config}
591     */
592    var min = 6;
593
594    /**
595     * @member {number} Hyphenator~orphanControl
596     * @desc
597     * Control how the last words of a line are handled:
598     * level 1 (default): last word is hyphenated
599     * level 2: last word is not hyphenated
600     * level 3: last word is not hyphenated and last space is non breaking
601     * @default 1
602     * @access private
603     */
604    var orphanControl = 1;
605
606    /**
607     * @member {boolean} Hyphenator~isBookmarklet
608     * @desc
609     * True if Hyphanetor runs as bookmarklet.
610     * @access private
611     */
612    var isBookmarklet = (function () {
613        var loc = null,
614            re = false,
615            scripts = contextWindow.document.getElementsByTagName('script'),
616            i = 0,
617            l = scripts.length;
618        while (!re && i < l) {
619            loc = scripts[i].getAttribute('src');
620            if (!!loc && loc.indexOf('Hyphenator.js?bm=true') !== -1) {
621                re = true;
622            }
623            i += 1;
624        }
625        return re;
626    }());
627
628    /**
629     * @member {string|null} Hyphenator~mainLanguage
630     * @desc
631     * The general language of the document. In contrast to {@link Hyphenator~defaultLanguage},
632     * mainLanguage is defined by the client (i.e. by the html or by a prompt).
633     * @access private
634     * @see {@link Hyphenator~autoSetMainLanguage}
635     */
636    var mainLanguage = null;
637
638    /**
639     * @member {string|null} Hyphenator~defaultLanguage
640     * @desc
641     * The language defined by the developper. This language setting is defined by a config option.
642     * It is overwritten by any html-lang-attribute and only taken in count, when no such attribute can
643     * be found (i.e. just before the prompt).
644     * @access private
645     * @see {@link Hyphenator.config}
646     * @see {@link Hyphenator~autoSetMainLanguage}
647     */
648    var defaultLanguage = '';
649
650    /**
651     * @member {ElementCollection} Hyphenator~elements
652     * @desc
653     * A class representing all elements (of type Element) that have to be hyphenated. This var is filled by
654     * {@link Hyphenator~gatherDocumentInfos}
655     * @access private
656     */
657    var elements = (function () {
658        /**
659         * @constructor Hyphenator~elements~ElementCollection~Element
660         * @desc represents a DOM Element with additional information
661         * @access private
662         */
663        var makeElement = function (element) {
664                return {
665                    /**
666                     * @member {Object} Hyphenator~elements~ElementCollection~Element~element
667                     * @desc A DOM Element
668                     * @access protected
669                     */
670                    element: element,
671                    /**
672                     * @member {boolean} Hyphenator~elements~ElementCollection~Element~hyphenated
673                     * @desc Marks if the element has been hyphenated
674                     * @access protected
675                     */
676                    hyphenated: false,
677                    /**
678                     * @member {boolean} Hyphenator~elements~ElementCollection~Element~treated
679                     * @desc Marks if information of the element has been collected but not hyphenated (e.g. dohyphenation is off)
680                     * @access protected
681                     */
682                    treated: false
683                };
684            },
685            /**
686             * @constructor Hyphenator~elements~ElementCollection
687             * @desc A collection of Elements to be hyphenated
688             * @access protected
689             */
690            makeElementCollection = function () {
691                    /**
692                     * @member {number} Hyphenator~elements~ElementCollection~counters
693                     * @desc Array of [number of collected elements, number of hyphenated elements]
694                     * @access protected
695                     */
696                var counters = [0, 0],
697                    /**
698                     * @member {Object.<string, Array.<Element>>} Hyphenator~elements~ElementCollection~list
699                     * @desc The collection of elements, where the key is a language code and the value is an array of elements
700                     * @access protected
701                     */
702                    list = {},
703                    /**
704                     * @method Hyphenator~elements~ElementCollection.prototype~add
705                     * @augments Hyphenator~elements~ElementCollection
706                     * @access protected
707                     * @desc adds a DOM element to the collection
708                     * @param {Object} el - The DOM element
709                     * @param {string} lang - The language of the element
710                     */
711                    add = function (el, lang) {
712                        var elo = makeElement(el);
713                        if (!list.hasOwnProperty(lang)) {
714                            list[lang] = [];
715                        }
716                        list[lang].push(elo);
717                        counters[0] += 1;
718                        return elo;
719                    },
720                    /**
721                     * @callback Hyphenator~elements~ElementCollection.prototype~each~callback fn - The callback that is executed for each element
722                     * @param {string} [k] The key (i.e. language) of the collection
723                     * @param {Hyphenator~elements~ElementCollection~Element} element
724                     */
725
726                    /**
727                     * @method Hyphenator~elements~ElementCollection.prototype~each
728                     * @augments Hyphenator~elements~ElementCollection
729                     * @access protected
730                     * @desc takes each element of the collection as an argument of fn
731                     * @param {Hyphenator~elements~ElementCollection.prototype~each~callback} fn - A function that takes an element as an argument
732                     */
733                    each = function (fn) {
734                        forEachKey(list, function (k) {
735                            if (fn.length === 2) {
736                                fn(k, list[k]);
737                            } else {
738                                fn(list[k]);
739                            }
740                        });
741                    };
742
743                return {
744                    counters: counters,
745                    list: list,
746                    add: add,
747                    each: each
748                };
749
750            };
751        return makeElementCollection();
752    }());
753
754
755    /**
756     * @member {Object.<sting, string>} Hyphenator~exceptions
757     * @desc
758     * An object containing exceptions as comma separated strings for each language.
759     * When the language-objects are loaded, their exceptions are processed, copied here and then deleted.
760     * Exceptions can also be set by the user.
761     * @see {@link Hyphenator~prepareLanguagesObj}
762     * @access private
763     */
764    var exceptions = {};
765
766    /**
767     * @member {Object.<string, boolean>} Hyphenator~docLanguages
768     * @desc
769     * An object holding all languages used in the document. This is filled by
770     * {@link Hyphenator~gatherDocumentInfos}
771     * @access private
772     */
773    var docLanguages = {};
774
775    /**
776     * @member {string} Hyphenator~url
777     * @desc
778     * A string containing a insane RegularExpression to match URL's
779     * @access private
780     */
781    var 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#!:\\.?\\+=&%@!\\-]*)*';
782    //      protocoll     usr     pwd                    ip               or                          host                 tld        port               path
783
784    /**
785     * @member {string} Hyphenator~mail
786     * @desc
787     * A string containing a RegularExpression to match mail-adresses
788     * @access private
789     */
790    var mail = '[\\w-\\.]+@[\\w\\.]+';
791
792    /**
793     * @member {string} Hyphenator~zeroWidthSpace
794     * @desc
795     * A string that holds a char.
796     * Depending on the browser, this is the zero with space or an empty string.
797     * zeroWidthSpace is used to break URLs
798     * @access private
799     */
800    var zeroWidthSpace = (function () {
801        var zws, ua = window.navigator.userAgent.toLowerCase();
802        zws = String.fromCharCode(8203); //Unicode zero width space
803        if (ua.indexOf('msie 6') !== -1) {
804            zws = ''; //IE6 doesn't support zws
805        }
806        if (ua.indexOf('opera') !== -1 && ua.indexOf('version/10.00') !== -1) {
807            zws = ''; //opera 10 on XP doesn't support zws
808        }
809        return zws;
810    }());
811
812    /**
813     * @method Hyphenator~onBeforeWordHyphenation
814     * @desc
815     * This method is called just before a word is hyphenated.
816     * It is called with two parameters: the word and its language.
817     * The method must return a string (aka the word).
818     * @see {@link Hyphenator.config}
819     * @access private
820     * @param {string} word
821     * @param {string} lang
822     * @return {string} The word that goes into hyphenation
823     */
824    var onBeforeWordHyphenation = function (word) {
825        return word;
826    };
827
828    /**
829     * @method Hyphenator~onAfterWordHyphenation
830     * @desc
831     * This method is called for each word after it is hyphenated.
832     * Takes the word as a first parameter and its language as a second parameter.
833     * Returns a string that will replace the word that has been hyphenated.
834     * @see {@link Hyphenator.config}
835     * @access private
836     * @param {string} word
837     * @param {string} lang
838     * @return {string} The word that goes into hyphenation
839     */
840    var onAfterWordHyphenation = function (word) {
841        return word;
842    };
843
844    /**
845     * @method Hyphenator~onHyphenationDone
846     * @desc
847     * A method to be called, when the last element has been hyphenated.
848     * If there are frames the method is called for each frame.
849     * Therefore the location.href of the contextWindow calling this method is given as a parameter
850     * @see {@link Hyphenator.config}
851     * @param {string} context
852     * @access private
853     */
854    var onHyphenationDone = function (context) {
855        return context;
856    };
857
858    /**
859     * @name Hyphenator~selectorFunction
860     * @desc
861     * A function set by the user that has to return a HTMLNodeList or array of Elements to be hyphenated.
862     * By default this is set to false so we can check if a selectorFunction is set…
863     * @see {@link Hyphenator.config}
864     * @see {@link Hyphenator~mySelectorFunction}
865     * @default false
866     * @type {function|boolean}
867     * @access private
868     */
869    var selectorFunction = false;
870
871    /**
872     * @name Hyphenator~flattenNodeList
873     * @desc
874     * Takes a nodeList and returns an array with all elements that are not contained by another element in the nodeList
875     * By using this function the elements returned by selectElements can be 'flattened'.
876     * @see {@link Hyphenator~selectElements}
877     * @param {nodeList} nl
878     * @return {Array} Array of 'parent'-elements
879     * @access private
880     */
881    function flattenNodeList(nl) {
882        var parentElements = [],
883            i = 1,
884            j = 0,
885            isParent = true;
886
887        parentElements.push(nl[0]); //add the first item, since this is always an parent
888
889        while (i < nl.length) { //cycle through nodeList
890            while (j < parentElements.length) { //cycle through parentElements
891                if (parentElements[j].contains(nl[i])) {
892                    isParent = false;
893                    break;
894                }
895                j += 1;
896            }
897            if (isParent) {
898                parentElements.push(nl[i]);
899            }
900            isParent = true;
901            i += 1;
902        }
903
904        return parentElements;
905    }
906
907    /**
908     * @method Hyphenator~mySelectorFunction
909     * @desc
910     * A function that returns a HTMLNodeList or array of Elements to be hyphenated.
911     * By default it uses the classname ('hyphenate') to select the elements.
912     * @access private
913     */
914    function mySelectorFunction(hyphenateClass) {
915        var tmp,
916            el = [],
917            i = 0;
918        if (window.document.getElementsByClassName) {
919            el = contextWindow.document.getElementsByClassName(hyphenateClass);
920        } else if (window.document.querySelectorAll) {
921            el = contextWindow.document.querySelectorAll('.' + hyphenateClass);
922        } else {
923            tmp = contextWindow.document.getElementsByTagName('*');
924            while (i < tmp.length) {
925                if (tmp[i].className.indexOf(hyphenateClass) !== -1 && tmp[i].className.indexOf(dontHyphenateClass) === -1) {
926                    el.push(tmp[i]);
927                }
928                i += 1;
929            }
930        }
931        return el;
932    }
933
934    /**
935     * @method Hyphenator~selectElements
936     * @desc
937     * A function that uses either selectorFunction set by the user
938     * or the default mySelectorFunction.
939     * @access private
940     */
941    function selectElements() {
942        var elems;
943        if (selectorFunction) {
944            elems = selectorFunction();
945        } else {
946            elems = mySelectorFunction(hyphenateClass);
947        }
948        if (elems.length !== 0) {
949            elems = flattenNodeList(elems);
950        }
951        return elems;
952    }
953
954    /**
955     * @member {string} Hyphenator~intermediateState
956     * @desc
957     * The visibility of elements while they are hyphenated:
958     * 'visible': unhyphenated text is visible and then redrawn when hyphenated.
959     * 'hidden': unhyphenated text is made invisible as soon as possible and made visible after hyphenation.
960     * @default 'hidden'
961     * @see {@link Hyphenator.config}
962     * @access private
963     */
964    var intermediateState = 'hidden';
965
966    /**
967     * @member {string} Hyphenator~unhide
968     * @desc
969     * How hidden elements unhide: either simultaneous (default: 'wait') or progressively.
970     * 'wait' makes Hyphenator.js to wait until all elements are hyphenated (one redraw)
971     * With 'progressive' Hyphenator.js unhides elements as soon as they are hyphenated.
972     * @see {@link Hyphenator.config}
973     * @access private
974     */
975    var unhide = 'wait';
976
977    /**
978     * @member {Array.<Hyphenator~CSSEdit>} Hyphenator~CSSEditors
979     * @desc A container array that holds CSSEdit classes
980     * For each window object one CSSEdit class is inserted
981     * @access private
982     */
983    var CSSEditors = [];
984
985    /**
986     * @constructor Hyphenator~CSSEdit
987     * @desc
988     * This class handles access and editing of StyleSheets.
989     * Thanks to this styles (e.g. hiding and unhiding elements upon hyphenation)
990     * can be changed in one place instead for each element.
991     * @access private
992     */
993    function makeCSSEdit(w) {
994        w = w || window;
995        var doc = w.document,
996            /**
997             * @member {Object} Hyphenator~CSSEdit~sheet
998             * @desc
999             * A StyleSheet, where Hyphenator can write to.
1000             * If no StyleSheet can be found, lets create one.
1001             * @access private
1002             */
1003            sheet = (function () {
1004                var i = 0,
1005                    l = doc.styleSheets.length,
1006                    s,
1007                    element,
1008                    r = false;
1009                while (i < l) {
1010                    s = doc.styleSheets[i];
1011                    try {
1012                        if (!!s.cssRules) {
1013                            r = s;
1014                            break;
1015                        }
1016                    } catch (ignore) {}
1017                    i += 1;
1018                }
1019                if (r === false) {
1020                    element = doc.createElement('style');
1021                    element.type = 'text/css';
1022                    doc.getElementsByTagName('head')[0].appendChild(element);
1023                    r = doc.styleSheets[doc.styleSheets.length - 1];
1024                }
1025                return r;
1026            }()),
1027
1028            /**
1029             * @typedef {Object} Hyphenator~CSSEdit~changes
1030             * @property {Object} sheet - The StyleSheet where the change was made
1031             * @property {number} index - The index of the changed rule
1032             */
1033
1034            /**
1035             * @member {Array.<changes>} Hyphenator~CSSEdit~changes
1036             * @desc
1037             * Sets a CSS rule for a specified selector
1038             * @access private
1039             */
1040            changes = [],
1041
1042            /**
1043             * @typedef Hyphenator~CSSEdit~rule
1044             * @property {number} index - The index of the rule
1045             * @property {Object} rule - The style rule
1046             */
1047            /**
1048             * @method Hyphenator~CSSEdit~findRule
1049             * @desc
1050             * Searches the StyleSheets for a given selector and returns an object containing the rule.
1051             * If nothing can be found, false is returned.
1052             * @param {string} sel
1053             * @return {Hyphenator~CSSEdit~rule|false}
1054             * @access private
1055             */
1056            findRule = function (sel) {
1057                var s,
1058                    rule,
1059                    sheets = w.document.styleSheets,
1060                    rules,
1061                    i = 0,
1062                    j = 0,
1063                    r = false;
1064                while (i < sheets.length) {
1065                    s = sheets[i];
1066                    try { //FF has issues here with external CSS (s.o.p)
1067                        if (!!s.cssRules) {
1068                            rules = s.cssRules;
1069                        } else if (!!s.rules) {
1070                            // IE < 9
1071                            rules = s.rules;
1072                        }
1073                    } catch (ignore) {}
1074                    if (!!rules && !!rules.length) {
1075                        while (j < rules.length) {
1076                            rule = rules[j];
1077                            if (rule.selectorText === sel) {
1078                                r = {
1079                                    index: j,
1080                                    rule: rule
1081                                };
1082                            }
1083                            j += 1;
1084                        }
1085                    }
1086                    i += 1;
1087                }
1088                return r;
1089            },
1090            /**
1091             * @method Hyphenator~CSSEdit~addRule
1092             * @desc
1093             * Adds a rule to the {@link Hyphenator~CSSEdit~sheet}
1094             * @param {string} sel - The selector to be added
1095             * @param {string} rulesStr - The rules for the specified selector
1096             * @return {number} index of the new rule
1097             * @access private
1098             */
1099            addRule = function (sel, rulesStr) {
1100                var i, r;
1101                if (!!sheet.insertRule) {
1102                    if (!!sheet.cssRules) {
1103                        i = sheet.cssRules.length;
1104                    } else {
1105                        i = 0;
1106                    }
1107                    r = sheet.insertRule(sel + '{' + rulesStr + '}', i);
1108                } else if (!!sheet.addRule) {
1109                    // IE < 9
1110                    if (!!sheet.rules) {
1111                        i = sheet.rules.length;
1112                    } else {
1113                        i = 0;
1114                    }
1115                    sheet.addRule(sel, rulesStr, i);
1116                    r = i;
1117                }
1118                return r;
1119            },
1120            /**
1121             * @method Hyphenator~CSSEdit~removeRule
1122             * @desc
1123             * Removes a rule with the specified index from the specified sheet
1124             * @param {Object} sheet - The style sheet
1125             * @param {number} index - the index of the rule
1126             * @access private
1127             */
1128            removeRule = function (sheet, index) {
1129                if (sheet.deleteRule) {
1130                    sheet.deleteRule(index);
1131                } else {
1132                    // IE < 9
1133                    sheet.removeRule(index);
1134                }
1135            };
1136
1137        return {
1138            /**
1139             * @method Hyphenator~CSSEdit.setRule
1140             * @desc
1141             * Sets a CSS rule for a specified selector
1142             * @access public
1143             * @param {string} sel - Selector
1144             * @param {string} rulesString - CSS-Rules
1145             */
1146            setRule: function (sel, rulesString) {
1147                var i, existingRule, cssText;
1148                existingRule = findRule(sel);
1149                if (!!existingRule) {
1150                    if (!!existingRule.rule.cssText) {
1151                        cssText = existingRule.rule.cssText;
1152                    } else {
1153                        // IE < 9
1154                        cssText = existingRule.rule.style.cssText.toLowerCase();
1155                    }
1156                    if (cssText !== sel + ' { ' + rulesString + ' }') {
1157                        //cssText of the found rule is not uniquely selector + rulesString,
1158                        if (cssText.indexOf(rulesString) !== -1) {
1159                            //maybe there are other rules or IE < 9
1160                            //clear existing def
1161                            existingRule.rule.style.visibility = '';
1162                        }
1163                        //add rule and register for later removal
1164                        i = addRule(sel, rulesString);
1165                        changes.push({sheet: sheet, index: i});
1166                    }
1167                } else {
1168                    i = addRule(sel, rulesString);
1169                    changes.push({sheet: sheet, index: i});
1170                }
1171            },
1172            /**
1173             * @method Hyphenator~CSSEdit.clearChanges
1174             * @desc
1175             * Removes all changes Hyphenator has made from the StyleSheets
1176             * @access public
1177             */
1178            clearChanges: function () {
1179                var change = changes.pop();
1180                while (!!change) {
1181                    removeRule(change.sheet, change.index);
1182                    change = changes.pop();
1183                }
1184            }
1185        };
1186    }
1187
1188    /**
1189     * @member {string} Hyphenator~hyphen
1190     * @desc
1191     * A string containing the character for in-word-hyphenation
1192     * @default the soft hyphen
1193     * @access private
1194     * @see {@link Hyphenator.config}
1195     */
1196    var hyphen = String.fromCharCode(173);
1197
1198    /**
1199     * @member {string} Hyphenator~urlhyphen
1200     * @desc
1201     * A string containing the character for url/mail-hyphenation
1202     * @default the zero width space
1203     * @access private
1204     * @see {@link Hyphenator.config}
1205     * @see {@link Hyphenator~zeroWidthSpace}
1206     */
1207    var urlhyphen = zeroWidthSpace;
1208
1209    /**
1210     * @method Hyphenator~hyphenateURL
1211     * @desc
1212     * Puts {@link Hyphenator~urlhyphen} (default: zero width space) after each no-alphanumeric char that my be in a URL.
1213     * @param {string} url to hyphenate
1214     * @returns string the hyphenated URL
1215     * @access public
1216     */
1217    function hyphenateURL(url) {
1218        var tmp = url.replace(/([:\/\.\?#&\-_,;!@]+)/gi, '$&' + urlhyphen),
1219            parts = tmp.split(urlhyphen),
1220            i = 0;
1221        while (i < parts.length) {
1222            if (parts[i].length > (2 * min)) {
1223                parts[i] = parts[i].replace(/(\w{3})(\w)/gi, "$1" + urlhyphen + "$2");
1224            }
1225            i += 1;
1226        }
1227        if (parts[parts.length - 1] === "") {
1228            parts.pop();
1229        }
1230        return parts.join(urlhyphen);
1231    }
1232
1233    /**
1234     * @member {boolean} Hyphenator~safeCopy
1235     * @desc
1236     * Defines wether work-around for copy issues is active or not
1237     * @default true
1238     * @access private
1239     * @see {@link Hyphenator.config}
1240     * @see {@link Hyphenator~registerOnCopy}
1241     */
1242    var safeCopy = true;
1243
1244    /**
1245     * @method Hyphenator~zeroTimeOut
1246     * @desc
1247     * defer execution of a function on the call stack
1248     * Analog to window.setTimeout(fn, 0) but without a clamped delay if postMessage is supported
1249     * @access private
1250     * @see {@link http://dbaron.org/log/20100309-faster-timeouts}
1251     */
1252    var zeroTimeOut = (function () {
1253        if (window.postMessage && window.addEventListener) {
1254            return (function () {
1255                var timeouts = [],
1256                    msg = "Hyphenator_zeroTimeOut_message",
1257                    setZeroTimeOut = function (fn) {
1258                        timeouts.push(fn);
1259                        window.postMessage(msg, "*");
1260                    },
1261                    handleMessage = function (event) {
1262                        if (event.source === window && event.data === msg) {
1263                            event.stopPropagation();
1264                            if (timeouts.length > 0) {
1265                                //var efn = timeouts.shift();
1266                                //efn();
1267                                timeouts.shift()();
1268                            }
1269                        }
1270                    };
1271                window.addEventListener("message", handleMessage, true);
1272                return setZeroTimeOut;
1273            }());
1274        }
1275        return function (fn) {
1276            window.setTimeout(fn, 0);
1277        };
1278    }());
1279
1280    /**
1281     * @member {Object} Hyphenator~hyphRunFor
1282     * @desc
1283     * stores location.href for documents where run() has been executed
1284     * to warn when Hyphenator.run() executed multiple times
1285     * @access private
1286     * @see {@link Hyphenator~runWhenLoaded}
1287     */
1288    var hyphRunFor = {};
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309    /**
1310     * @method Hyphenator~runWhenLoaded
1311     * @desc
1312     * A crossbrowser solution for the DOMContentLoaded-Event based on
1313     * <a href = "http://jquery.com/">jQuery</a>
1314     * I added some functionality: e.g. support for frames and iframes…
1315     * @param {Object} w the window-object
1316     * @param {function()} f the function to call when the document is ready
1317     * @access private
1318     */
1319    function runWhenLoaded(w, f) {
1320        var toplevel,
1321            add = window.document.addEventListener
1322                ? 'addEventListener'
1323                : 'attachEvent',
1324            rem = window.document.addEventListener
1325                ? 'removeEventListener'
1326                : 'detachEvent',
1327            pre = window.document.addEventListener
1328                ? ''
1329                : 'on';
1330
1331        function init(context) {
1332            if (hyphRunFor[context.location.href]) {
1333                onWarning(new Error("Warning: multiple execution of Hyphenator.run() – This may slow down the script!"));
1334            }
1335            contextWindow = context || window;
1336            f();
1337            hyphRunFor[contextWindow.location.href] = true;
1338        }
1339
1340        function doScrollCheck() {
1341            try {
1342                // If IE is used, use the trick by Diego Perini
1343                // http://javascript.nwbox.com/IEContentLoaded/
1344                w.document.documentElement.doScroll("left");
1345            } catch (ignore) {
1346                window.setTimeout(doScrollCheck, 1);
1347                return;
1348            }
1349            //maybe modern IE fired DOMContentLoaded
1350            if (!hyphRunFor[w.location.href]) {
1351                documentLoaded = true;
1352                init(w);
1353            }
1354        }
1355
1356        function doOnEvent(e) {
1357            var i = 0,
1358                fl,
1359                haveAccess;
1360            if (!!e && e.type === 'readystatechange' && w.document.readyState !== 'interactive' && w.document.readyState !== 'complete') {
1361                return;
1362            }
1363
1364            //DOM is ready/interactive, but frames may not be loaded yet!
1365            //cleanup events
1366            w.document[rem](pre + 'DOMContentLoaded', doOnEvent, false);
1367            w.document[rem](pre + 'readystatechange', doOnEvent, false);
1368
1369            //check frames
1370            fl = w.frames.length;
1371            if (fl === 0 || !doFrames) {
1372                //there are no frames!
1373                //cleanup events
1374                w[rem](pre + 'load', doOnEvent, false);
1375                documentLoaded = true;
1376                init(w);
1377            } else if (doFrames && fl > 0) {
1378                //we have frames, so wait for onload and then initiate runWhenLoaded recursevly for each frame:
1379                if (!!e && e.type === 'load') {
1380                    //cleanup events
1381                    w[rem](pre + 'load', doOnEvent, false);
1382                    while (i < fl) {
1383                        haveAccess = undefined;
1384                        //try catch isn't enough for webkit
1385                        try {
1386                            //opera throws only on document.toString-access
1387                            haveAccess = w.frames[i].document.toString();
1388                        } catch (ignore) {
1389                            haveAccess = undefined;
1390                        }
1391                        if (!!haveAccess) {
1392                            runWhenLoaded(w.frames[i], f);
1393                        }
1394                        i += 1;
1395                    }
1396                    init(w);
1397                }
1398            }
1399        }
1400
1401        if (documentLoaded || w.document.readyState === 'complete') {
1402            //Hyphenator has run already (documentLoaded is true) or
1403            //it has been loaded after onLoad
1404            documentLoaded = true;
1405            doOnEvent({type: 'load'});
1406        } else {
1407            //register events
1408            w.document[add](pre + 'DOMContentLoaded', doOnEvent, false);
1409            w.document[add](pre + 'readystatechange', doOnEvent, false);
1410            w[add](pre + 'load', doOnEvent, false);
1411            toplevel = false;
1412            try {
1413                toplevel = !window.frameElement;
1414            } catch (ignore) {}
1415            if (toplevel && w.document.documentElement.doScroll) {
1416                doScrollCheck(); //calls init()
1417            }
1418        }
1419    }
1420
1421    /**
1422     * @method Hyphenator~getLang
1423     * @desc
1424     * Gets the language of an element. If no language is set, it may use the {@link Hyphenator~mainLanguage}.
1425     * @param {Object} el The first parameter is an DOM-Element-Object
1426     * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator~mainLanguage}
1427     * if there's no language found for the element.
1428     * @return {string} The language of the element
1429     * @access private
1430     */
1431    function getLang(el, fallback) {
1432        try {
1433            return !!el.getAttribute('lang')
1434                ? el.getAttribute('lang').toLowerCase()
1435                : !!el.getAttribute('xml:lang')
1436                    ? el.getAttribute('xml:lang').toLowerCase()
1437                    : el.tagName.toLowerCase() !== 'html'
1438                        ? getLang(el.parentNode, fallback)
1439                        : fallback
1440                            ? mainLanguage
1441                            : null;
1442        } catch (ignore) {}
1443    }
1444
1445    /**
1446     * @method Hyphenator~autoSetMainLanguage
1447     * @desc
1448     * Retrieves the language of the document from the DOM and sets the lang attribute of the html-tag.
1449     * The function looks in the following places:
1450     * <ul>
1451     * <li>lang-attribute in the html-tag</li>
1452     * <li>&lt;meta http-equiv = "content-language" content = "xy" /&gt;</li>
1453     * <li>&lt;meta name = "DC.Language" content = "xy" /&gt;</li>
1454     * <li>&lt;meta name = "language" content = "xy" /&gt;</li>
1455     * </li>
1456     * If nothing can be found a prompt using {@link Hyphenator~languageHint} and a prompt-string is displayed.
1457     * If the retrieved language is in the object {@link Hyphenator~supportedLangs} it is copied to {@link Hyphenator~mainLanguage}
1458     * @access private
1459     */
1460    function autoSetMainLanguage(w) {
1461        w = w || contextWindow;
1462        var el = w.document.getElementsByTagName('html')[0],
1463            m = w.document.getElementsByTagName('meta'),
1464            i = 0,
1465            getLangFromUser = function () {
1466                var ml,
1467                    text = '',
1468                    dH = 300,
1469                    dW = 450,
1470                    dX = Math.floor((w.outerWidth - dW) / 2) + window.screenX,
1471                    dY = Math.floor((w.outerHeight - dH) / 2) + window.screenY,
1472                    ul = '',
1473                    languageHint;
1474                if (!!window.showModalDialog && (w.location.href.indexOf(basePath) !== -1)) {
1475                    ml = window.showModalDialog(basePath + 'modalLangDialog.html', supportedLangs, "dialogWidth: " + dW + "px; dialogHeight: " + dH + "px; dialogtop: " + dY + "; dialogleft: " + dX + "; center: on; resizable: off; scroll: off;");
1476                } else {
1477                    languageHint = (function () {
1478                        var r = '';
1479                        forEachKey(supportedLangs, function (k) {
1480                            r += k + ', ';
1481                        });
1482                        r = r.substring(0, r.length - 2);
1483                        return r;
1484                    }());
1485                    ul = window.navigator.language || window.navigator.userLanguage;
1486                    ul = ul.substring(0, 2);
1487                    if (!!supportedLangs[ul] && supportedLangs[ul].prompt !== '') {
1488                        text = supportedLangs[ul].prompt;
1489                    } else {
1490                        text = supportedLangs.en.prompt;
1491                    }
1492                    text += ' (ISO 639-1)\n\n' + languageHint;
1493                    ml = window.prompt(window.unescape(text), ul).toLowerCase();
1494                }
1495                return ml;
1496            };
1497        mainLanguage = getLang(el, false);
1498        if (!mainLanguage) {
1499            while (i < m.length) {
1500                //<meta http-equiv = "content-language" content="xy">
1501                if (!!m[i].getAttribute('http-equiv') && (m[i].getAttribute('http-equiv').toLowerCase() === 'content-language')) {
1502                    mainLanguage = m[i].getAttribute('content').toLowerCase();
1503                }
1504                //<meta name = "DC.Language" content="xy">
1505                if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'dc.language')) {
1506                    mainLanguage = m[i].getAttribute('content').toLowerCase();
1507                }
1508                //<meta name = "language" content = "xy">
1509                if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'language')) {
1510                    mainLanguage = m[i].getAttribute('content').toLowerCase();
1511                }
1512                i += 1;
1513            }
1514        }
1515        //get lang for frame from enclosing document
1516        if (!mainLanguage && doFrames && (!!contextWindow.frameElement)) {
1517            autoSetMainLanguage(window.parent);
1518        }
1519        //fallback to defaultLang if set
1520        if (!mainLanguage && defaultLanguage !== '') {
1521            mainLanguage = defaultLanguage;
1522        }
1523        //ask user for lang
1524        if (!mainLanguage) {
1525            mainLanguage = getLangFromUser();
1526        }
1527        el.lang = mainLanguage;
1528    }
1529
1530    /**
1531     * @method Hyphenator~gatherDocumentInfos
1532     * @desc
1533     * This method runs through the DOM and executes the process()-function on:
1534     * - every node returned by the {@link Hyphenator~selectorFunction}.
1535     * @access private
1536     */
1537    function gatherDocumentInfos() {
1538        var elToProcess,
1539            urlhyphenEls,
1540            tmp,
1541            i = 0;
1542        /**
1543         * @method Hyphenator~gatherDocumentInfos
1544         * @desc
1545         * This method copies the element to the elements-variable, sets its visibility
1546         * to intermediateState, retrieves its language and recursivly descends the DOM-tree until
1547         * the child-Nodes aren't of type 1
1548         * @param {Object} el a DOM element
1549         * @param {string} plang the language of the parent element
1550         * @param {boolean} isChild true, if the parent of el has been processed
1551         */
1552        function process(el, pLang, isChild) {
1553            var n,
1554                j = 0,
1555                hyphenate = true,
1556                eLang,
1557                useCSS3 = function () {
1558                    css3hyphenateClassHandle = makeCSSEdit(contextWindow);
1559                    css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': auto;');
1560                    css3hyphenateClassHandle.setRule('.' + dontHyphenateClass, css3_h9n.property + ': manual;');
1561                    if ((eLang !== pLang) && css3_h9n.property.indexOf('webkit') !== -1) {
1562                        css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, '-webkit-locale : ' + eLang + ';');
1563                    }
1564                    el.className = el.className + ' ' + css3hyphenateClass;
1565                },
1566                useHyphenator = function () {
1567                    //quick fix for test111.html
1568                    //better: weight elements
1569                    if (isBookmarklet && eLang !== mainLanguage) {
1570                        return;
1571                    }
1572                    if (supportedLangs.hasOwnProperty(eLang)) {
1573                        docLanguages[eLang] = true;
1574                    } else {
1575                        if (supportedLangs.hasOwnProperty(eLang.split('-')[0])) { //try subtag
1576                            eLang = eLang.split('-')[0];
1577                            docLanguages[eLang] = true;
1578                        } else if (!isBookmarklet) {
1579                            hyphenate = false;
1580                            onError(new Error('Language "' + eLang + '" is not yet supported.'));
1581                        }
1582                    }
1583                    if (hyphenate) {
1584                        if (intermediateState === 'hidden') {
1585                            el.className = el.className + ' ' + hideClass;
1586                        }
1587                        elements.add(el, eLang);
1588                    }
1589                };
1590            isChild = isChild || false;
1591            if (el.lang && typeof el.lang === 'string') {
1592                eLang = el.lang.toLowerCase(); //copy attribute-lang to internal eLang
1593            } else if (!!pLang && pLang !== '') {
1594                eLang = pLang.toLowerCase();
1595            } else {
1596                eLang = getLang(el, true);
1597            }
1598
1599            if (!isChild) {
1600                if (css3 && css3_h9n.support && !!css3_h9n.checkLangSupport(eLang)) {
1601                    useCSS3();
1602                } else {
1603                    useHyphenator();
1604                }
1605            } else {
1606                if (eLang !== pLang) {
1607                    if (css3 && css3_h9n.support && !!css3_h9n.checkLangSupport(eLang)) {
1608                        useCSS3();
1609                    } else {
1610                        useHyphenator();
1611                    }
1612                } else {
1613                    if (!css3 || !css3_h9n.support || !css3_h9n.checkLangSupport(eLang)) {
1614                        useHyphenator();
1615                    } // else do nothing
1616                }
1617            }
1618            n = el.childNodes[j];
1619            while (!!n) {
1620                if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] &&
1621                        n.className.indexOf(dontHyphenateClass) === -1 &&
1622                        n.className.indexOf(urlHyphenateClass) === -1 && !elToProcess[n]) {
1623                    process(n, eLang, true);
1624                }
1625                j += 1;
1626                n = el.childNodes[j];
1627            }
1628        }
1629        function processUrlStyled(el) {
1630            var n, j = 0;
1631
1632            n = el.childNodes[j];
1633            while (!!n) {
1634                if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] &&
1635                        n.className.indexOf(dontHyphenateClass) === -1 &&
1636                        n.className.indexOf(hyphenateClass) === -1 && !urlhyphenEls[n]) {
1637                    processUrlStyled(n);
1638                } else if (n.nodeType === 3) {
1639                    n.data = hyphenateURL(n.data);
1640                }
1641                j += 1;
1642                n = el.childNodes[j];
1643            }
1644        }
1645
1646        if (css3) {
1647            css3_h9n = css3_gethsupport();
1648        }
1649        if (isBookmarklet) {
1650            elToProcess = contextWindow.document.getElementsByTagName('body')[0];
1651            process(elToProcess, mainLanguage, false);
1652        } else {
1653            if (!css3 && intermediateState === 'hidden') {
1654                CSSEditors.push(makeCSSEdit(contextWindow));
1655                CSSEditors[CSSEditors.length - 1].setRule('.' + hyphenateClass, 'visibility: hidden;');
1656                CSSEditors[CSSEditors.length - 1].setRule('.' + hideClass, 'visibility: hidden;');
1657                CSSEditors[CSSEditors.length - 1].setRule('.' + unhideClass, 'visibility: visible;');
1658            }
1659            elToProcess = selectElements();
1660            tmp = elToProcess[i];
1661            while (!!tmp) {
1662                process(tmp, '', false);
1663                i += 1;
1664                tmp = elToProcess[i];
1665            }
1666
1667            urlhyphenEls = mySelectorFunction(urlHyphenateClass);
1668            i = 0;
1669            tmp = urlhyphenEls[i];
1670            while (!!tmp) {
1671                processUrlStyled(tmp);
1672                i += 1;
1673                tmp = urlhyphenEls[i];
1674            }
1675        }
1676        if (elements.counters[0] === 0) {
1677            //nothing to hyphenate or all hyphenated by css3
1678            i = 0;
1679            while (i < CSSEditors.length) {
1680                CSSEditors[i].clearChanges();
1681                i += 1;
1682            }
1683            onHyphenationDone(contextWindow.location.href);
1684        }
1685    }
1686
1687    /**
1688     * @method Hyphenator~createCharMap
1689     * @desc
1690     * reads the charCodes from lo.characters and stores them in a bidi map:
1691     * charMap.int2code =  [0: 97, //a
1692     *                      1: 98, //b
1693     *                      2: 99] //c etc.
1694     * charMap.code2int = {"97": 0, //a
1695     *                     "98": 1, //b
1696     *                     "99": 2} //c etc.
1697     * @access private
1698     * @param {Object} language object
1699     */
1700    function makeCharMap() {
1701        var int2code = [],
1702            code2int = {},
1703            add = function (newValue) {
1704                if (!code2int[newValue]) {
1705                    int2code.push(newValue);
1706                    code2int[newValue] = int2code.length - 1;
1707                }
1708            };
1709        return {
1710            int2code: int2code,
1711            code2int: code2int,
1712            add: add
1713        };
1714    }
1715
1716    /**
1717     * @constructor Hyphenator~ValueStore
1718     * @desc Storage-Object for storing hyphenation points (aka values)
1719     * @access private
1720     */
1721    function makeValueStore(len) {
1722        //var indexes = [1, 2, 2],
1723        var indexes = (function () {
1724                var arr;
1725                if (Object.prototype.hasOwnProperty.call(window, "Uint32Array")) { //IE<9 doesn't have window.hasOwnProperty (host object)
1726                    arr = new window.Uint32Array(3);
1727                    arr[0] = 1;
1728                    arr[1] = 2;
1729                    arr[2] = 2;
1730                } else {
1731                    arr = [1, 2, 2];
1732                }
1733                return arr;
1734            }()),
1735            keys = (function () {
1736                var i, r;
1737                if (Object.prototype.hasOwnProperty.call(window, "Uint8Array")) { //IE<9 doesn't have window.hasOwnProperty (host object)
1738                    return new window.Uint8Array(len);
1739                }
1740                r = [];
1741                r.length = len;
1742                i = r.length - 1;
1743                while (i >= 0) {
1744                    r[i] = 0;
1745                    i -= 1;
1746                }
1747                return r;
1748            }()),
1749            add = function (p) {
1750                keys[indexes[1]] = p;
1751                indexes[2] = indexes[1];
1752                indexes[1] += 1;
1753            },
1754            add0 = function () {
1755                //just do a step, since array is initialized with zeroe
1755s
1756                indexes[1] += 1;
1757            },
1758            finalize = function () {
1759                var start = indexes[0];
1760                keys[start] = indexes[2] - start;
1761                indexes[0] = indexes[2] + 1;
1762                indexes[1] = indexes[2] + 2;
1763                return start;
1764            };
1765        return {
1766            keys: keys,
1767            add: add,
1768            add0: add0,
1769            finalize: finalize
1770        };
1771    }
1772
1773    /**
1774     * @method Hyphenator~convertPatternsToArray
1775     * @desc
1776     * converts the patterns to a (typed, if possible) array as described by Liang:
1777     *
1778     * 1. Create the CharMap: an alphabet of used character codes mapped to an int (e.g. a: "97" -> 0)
1779     *    This map is bidirectional:
1780     *    charMap.code2int is an object with charCodes as keys and corresponging ints as values
1781     *    charMao.int2code is an array of charCodes at int indizes
1782     *    the length of charMao.int2code is equal the length of the alphabet
1783     *
1784     * 2. Create a ValueStore: (typed) array that holds "values", i.e. the digits extracted from the patterns
1785     *    The first value starts at index 1 (since the trie is initialized with zeroes, starting at 0 would create errors)
1786     *    Each value starts with its length at index i, actual values are stored in i + n where n < length
1787     *    Trailing 0 are not stored. So pattern values like e.g. "010200" will become […,4,0,1,0,2,…]
1788     *    The ValueStore-Object manages handling of indizes automatically. Use ValueStore.add(p) to add a running value.
1789     *    Use ValueStore.finalize() when the last value of a pattern is added. It will set the length and return the starting index of the pattern.
1790     *    To prevent doubles we could temporarly store the values in a object {value: startIndex} and only add new values,
1791     *    but this object deoptimizes very fast (new hidden map for each entry); here we gain speed and pay memory
1792     *
1793     * 3. Create and zero initialize a (typed) array to store the trie. The trie uses two slots for each entry/node:
1794     *    i: a link to another position in the array or -1 if the pattern ends here or more rows have to be added.
1795     *    i + 1: a link to a value in the ValueStore or 0 if there's no value for the path to this node.
1796     *    Although the array is one-dimensional it can be described as an array of "rows",
1797     *    where each "row" is an array of length trieRowLength (see below).
1798     *    The first entry of this "row" represents the first character of the alphabet, the second a possible link to value store,
1799     *    the third represents the second character of the alphabet and so on…
1800     *
1801     * 4. Initialize trieRowLength (length of the alphabet * 2)
1802     *
1803     * 5. Now we apply extract to each pattern collection (patterns of the same length are collected and concatenated to one string)
1804     *    extract goes through these pattern collections char by char and adds them either to the ValueStore (if they are digits) or
1805     *    to the trie (adding more "rows" if necessary, i.e. if the last link pointed to -1).
1806     *    So the first "row" holds all starting characters, where the subsequent rows hold the characters that follow the
1807     *    character that link to this row. Therefor the array is dense at the beginning and very sparse at the end.
1808     *
1809     *
1810     * @access private
1811     * @param {Object} language object
1812     */
1813    function convertPatternsToArray(lo) {
1814        var trieNextEmptyRow = 0,
1815            i,
1816            charMapc2i,
1817            valueStore,
1818            indexedTrie,
1819            trieRowLength,
1820
1821            extract = function (patternSizeInt, patterns) {
1822                var charPos = 0,
1823                    charCode = 0,
1824                    mappedCharCode = 0,
1825                    rowStart = 0,
1826                    nextRowStart = 0,
1827                    prevWasDigit = false;
1828                while (charPos < patterns.length) {
1829                    charCode = patterns.charCodeAt(charPos);
1830                    if ((charPos + 1) % patternSizeInt !== 0) {
1831                        //more to come…
1832                        if (charCode <= 57 && charCode >= 49) {
1833                            //charCode is a digit
1834                            valueStore.add(charCode - 48);
1835                            prevWasDigit = true;
1836                        } else {
1837                            //charCode is alphabetical
1838                            if (!prevWasDigit) {
1839                                valueStore.add0();
1840                            }
1841                            prevWasDigit = false;
1842                            if (nextRowStart === -1) {
1843                                nextRowStart = trieNextEmptyRow + trieRowLength;
1844                                trieNextEmptyRow = nextRowStart;
1845                                indexedTrie[rowStart + mappedCharCode * 2] = nextRowStart;
1846                            }
1847                            mappedCharCode = charMapc2i[charCode];
1848                            rowStart = nextRowStart;
1849                            nextRowStart = indexedTrie[rowStart + mappedCharCode * 2];
1850                            if (nextRowStart === 0) {
1851                                indexedTrie[rowStart + mappedCharCode * 2] = -1;
1852                                nextRowStart = -1;
1853                            }
1854                        }
1855                    } else {
1856                        //last part of pattern
1857                        if (charCode <= 57 && charCode >= 49) {
1858                            //the last charCode is a digit
1859                            valueStore.add(charCode - 48);
1860                            indexedTrie[rowStart + mappedCharCode * 2 + 1] = valueStore.finalize();
1861                        } else {
1862                            //the last charCode is alphabetical
1863                            if (!prevWasDigit) {
1864                                valueStore.add0();
1865                            }
1866                            valueStore.add0();
1867                            if (nextRowStart === -1) {
1868                                nextRowStart = trieNextEmptyRow + trieRowLength;
1869                                trieNextEmptyRow = nextRowStart;
1870                                indexedTrie[rowStart + mappedCharCode * 2] = nextRowStart;
1871                            }
1872                            mappedCharCode = charMapc2i[charCode];
1873                            rowStart = nextRowStart;
1874                            if (indexedTrie[rowStart + mappedCharCode * 2] === 0) {
1875                                indexedTrie[rowStart + mappedCharCode * 2] = -1;
1876                            }
1877                            indexedTrie[rowStart + mappedCharCode * 2 + 1] = valueStore.finalize();
1878                        }
1879                        rowStart = 0;
1880                        nextRowStart = 0;
1881                        prevWasDigit = false;
1882                    }
1883                    charPos += 1;
1884                }
1885            };/*,
1886            prettyPrintIndexedTrie = function (rowLength) {
1887                var s = "0: ",
1888                    idx;
1889                for (idx = 0; idx < indexedTrie.length; idx += 1) {
1890                    s += indexedTrie[idx];
1891                    s += ",";
1892                    if ((idx + 1) % rowLength === 0) {
1893                        s += "\n" + (idx + 1) + ": ";
1894                    }
1895                }
1896                console.log(s);
1897            };*/
1898
1899        lo.charMap = makeCharMap();
1900        i = 0;
1901        while (i < lo.patternChars.length) {
1902            lo.charMap.add(lo.patternChars.charCodeAt(i));
1903            i += 1;
1904        }
1905        charMapc2i = lo.charMap.code2int;
1906
1907        valueStore = makeValueStore(lo.valueStoreLength);
1908        lo.valueStore = valueStore;
1909
1910        if (Object.prototype.hasOwnProperty.call(window, "Int32Array")) { //IE<9 doesn't have window.hasOwnProperty (host object)
1911            lo.indexedTrie = new window.Int32Array(lo.patternArrayLength * 2);
1912        } else {
1913            lo.indexedTrie = [];
1914            lo.indexedTrie.length = lo.patternArrayLength * 2;
1915            i = lo.indexedTrie.length - 1;
1916            while (i >= 0) {
1917                lo.indexedTrie[i] = 0;
1918                i -= 1;
1919            }
1920        }
1921        indexedTrie = lo.indexedTrie;
1922        trieRowLength = lo.charMap.int2code.length * 2;
1923
1924        forEachKey(lo.patterns, function (i) {
1925            extract(parseInt(i, 10), lo.patterns[i]);
1926        });
1927        //prettyPrintIndexedTrie(lo.charMap.int2code.length * 2);
1928    }
1929
1930    /**
1931     * @method Hyphenator~recreatePattern
1932     * @desc
1933     * Recreates the pattern for the reducedPatternSet
1934     * @param {string} pattern The pattern (chars)
1935     * @param {string} nodePoints The nodePoints (integers)
1936     * @access private
1937     * @return {string} The pattern (chars and numbers)
1938     */
1939    function recreatePattern(pattern, nodePoints) {
1940        var r = [],
1941            c = pattern.split(''),
1942            i = 0;
1943        while (i <= c.length) {
1944            if (nodePoints[i] && nodePoints[i] !== 0) {
1945                r.push(nodePoints[i]);
1946            }
1947            if (c[i]) {
1948                r.push(c[i]);
1949            }
1950            i += 1;
1951        }
1952        return r.join('');
1953    }
1954
1955    /**
1956     * @method Hyphenator~convertExceptionsToObject
1957     * @desc
1958     * Converts a list of comma seprated exceptions to an object:
1959     * 'Fortran,Hy-phen-a-tion' -> {'Fortran':'Fortran','Hyphenation':'Hy-phen-a-tion'}
1960     * @access private
1961     * @param {string} exc a comma separated string of exceptions (without spaces)
1962     * @return {Object.<string, string>}
1963     */
1964    function convertExceptionsToObject(exc) {
1965        var w = exc.split(', '),
1966            r = {},
1967            i = 0,
1968            l = w.length,
1969            key;
1970        while (i < l) {
1971            key = w[i].replace(/-/g, '');
1972            if (!r.hasOwnProperty(key)) {
1973                r[key] = w[i];
1974            }
1975            i += 1;
1976        }
1977        return r;
1978    }
1979
1980    /**
1981     * @method Hyphenator~loadPatterns
1982     * @desc
1983     * Checks if the requested file is available in the network.
1984     * Adds a &lt;script&gt;-Tag to the DOM to load an externeal .js-file containing patterns and settings for the given language.
1985     * If the given language is not in the {@link Hyphenator~supportedLangs}-Object it returns.
1986     * One may ask why we are not using AJAX to load the patterns. The XMLHttpRequest-Object
1987     * has a same-origin-policy. This makes the Bookmarklet impossible.
1988     * @param {string} lang The language to load the patterns for
1989     * @access private
1990     * @see {@link Hyphenator~basePath}
1991     */
1992    function loadPatterns(lang, cb) {
1993        var location, xhr, head, script, done = false;
1994        if (supportedLangs.hasOwnProperty(lang) && !Hyphenator.languages[lang]) {
1995            location = basePath + 'patterns/' + supportedLangs[lang].file;
1996        } else {
1997            return;
1998        }
1999        if (isLocal && !isBookmarklet) {
2000            //check if 'location' is available:
2001            xhr = null;
2002            try {
2003                // Mozilla, Opera, Safari and Internet Explorer (ab v7)
2004                xhr = new window.XMLHttpRequest();
2005            } catch (ignore) {
2006                try {
2007                    //IE>=6
2008                    xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
2009                } catch (ignore) {
2010                    try {
2011                        //IE>=5
2012                        xhr = new window.ActiveXObject("Msxml2.XMLHTTP");
2013                    } catch (ignore) {
2014                        xhr = null;
2015                    }
2016                }
2017            }
2018
2019            if (xhr) {
2020                xhr.open('HEAD', location, true);
2021                xhr.setRequestHeader('Cache-Control', 'no-cache');
2022                xhr.onreadystatechange = function () {
2023                    if (xhr.readyState === 2) {
2024                        if (xhr.status >= 400) {
2025                            onError(new Error('Could not load\n' + location));
2026                            delete docLanguages[lang];
2027                            return;
2028                        }
2029                        xhr.abort();
2030                    }
2031                };
2032                xhr.send(null);
2033            }
2034        }
2035        if (createElem) {
2036            head = window.document.getElementsByTagName('head').item(0);
2037            script = createElem('script', window);
2038            script.src = location;
2039            script.type = 'text/javascript';
2040            script.charset = 'utf8';
2041            script.onreadystatechange = function () {
2042                if (!done && (!script.readyState || script.readyState === "loaded" || script.readyState === "complete")) {
2043                    done = true;
2044
2045                    cb();
2046
2047                    // Handle memory leak in IE
2048                    script.onreadystatechange = null;
2049                    script.onload = null;
2050                    if (head && script.parentNode) {
2051                        head.removeChild(script);
2052                    }
2053                }
2054            };
2055            script.onload = script.onreadystatechange;
2056            head.appendChild(script);
2057        }
2058    }
2059
2060    /**
2061     * @method Hyphenator~prepareLanguagesObj
2062     * @desc
2063     * Adds some feature to the language object:
2064     * - cache
2065     * - exceptions
2066     * Converts the patterns to a trie using {@link Hyphenator~convertPatterns}
2067     * @access private
2068     * @param {string} lang The language of the language object
2069     */
2070    function prepareLanguagesObj(lang) {
2071        var lo = Hyphenator.languages[lang], wrd;
2072
2073        if (!lo.prepared) {
2074            if (enableCache) {
2075                lo.cache = {};
2076                //Export
2077                //lo['cache'] = lo.cache;
2078            }
2079            if (enableReducedPatternSet) {
2080                lo.redPatSet = {};
2081            }
2082            //add exceptions from the pattern file to the local 'exceptions'-obj
2083            if (lo.hasOwnProperty('exceptions')) {
2084                Hyphenator.addExceptions(lang, lo.exceptions);
2085                delete lo.exceptions;
2086            }
2087            //copy global exceptions to the language specific exceptions
2088            if (exceptions.hasOwnProperty('global')) {
2089                if (exceptions.hasOwnProperty(lang)) {
2090                    exceptions[lang] += ', ' + exceptions.global;
2091                } else {
2092                    exceptions[lang] = exceptions.global;
2093                }
2094            }
2095            //move exceptions from the the local 'exceptions'-obj to the 'language'-object
2096            if (exceptions.hasOwnProperty(lang)) {
2097                lo.exceptions = convertExceptionsToObject(exceptions[lang]);
2098                delete exceptions[lang];
2099            } else {
2100                lo.exceptions = {};
2101            }
2102            convertPatternsToArray(lo);
2103            if (String.prototype.normalize) {
2104                wrd = '[\\w' + lo.specialChars + lo.specialChars.normalize("NFD") + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
2105            } else {
2106                wrd = '[\\w' + lo.specialChars + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
2107            }
2108            lo.genRegExp = new RegExp('(' + wrd + ')|(' + url + ')|(' + mail + ')', 'gi');
2109            lo.prepared = true;
2110        }
2111    }
2112
2113    /****
2114     * @method Hyphenator~prepare
2115     * @desc
2116     * This funtion prepares the Hyphenator~Object: If RemoteLoading is turned off, it assumes
2117     * that the patternfiles are loaded, all conversions are made and the callback is called.
2118     * If storage is active the object is retrieved there.
2119     * If RemoteLoading is on (default), it loads the pattern files and repeatedly checks Hyphenator.languages.
2120     * If a patternfile is loaded the patterns are stored in storage (if enabled),
2121     * converted to their object style and the lang-object extended.
2122     * Finally the callback is called.
2123     * @access private
2124     */
2125    function prepare(callback) {
2126        var tmp1;
2127
2128        function languagesLoaded() {
2129            forEachKey(docLanguages, function (l) {
2130                if (Hyphenator.languages.hasOwnProperty(l)) {
2131                    delete docLanguages[l];
2132                    if (!!storage) {
2133                        storage.setItem(l, window.JSON.stringify(Hyphenator.languages[l]));
2134                    }
2135                    prepareLanguagesObj(l);
2136                    callback(l);
2137                }
2138            });
2139        }
2140
2141        if (!enableRemoteLoading) {
2142            forEachKey(Hyphenator.languages, function (lang) {
2143                prepareLanguagesObj(lang);
2144            });
2145            callback('*');
2146            return;
2147        }
2148        // get all languages that are used and preload the patterns
2149        forEachKey(docLanguages, function (lang) {
2150            if (!!storage && storage.test(lang)) {
2151                Hyphenator.languages[lang] = window.JSON.parse(storage.getItem(lang));
2152                prepareLanguagesObj(lang);
2153                if (exceptions.hasOwnProperty('global')) {
2154                    tmp1 = convertExceptionsToObject(exceptions.global);
2155                    forEachKey(tmp1, function (tmp2) {
2156                        Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
2157                    });
2158                }
2159                //Replace exceptions since they may have been changed:
2160                if (exceptions.hasOwnProperty(lang)) {
2161                    tmp1 = convertExceptionsToObject(exceptions[lang]);
2162                    forEachKey(tmp1, function (tmp2) {
2163                        Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
2164                    });
2165                    delete exceptions[lang];
2166                }
2167                //Replace genRegExp since it may have been changed:
2168                if (String.prototype.normalize) {
2169                    tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + Hyphenator.languages[lang].specialChars.normalize("NFD") + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
2170                } else {
2171                    tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
2172                }
2173                Hyphenator.languages[lang].genRegExp = new RegExp('(' + tmp1 + ')|(' + url + ')|(' + mail + ')', 'gi');
2174                if (enableCache) {
2175                    if (!Hyphenator.languages[lang].cache) {
2176                        Hyphenator.languages[lang].cache = {};
2177                    }
2178                }
2179                delete docLanguages[lang];
2180                callback(lang);
2181            } else {
2182                loadPatterns(lang, languagesLoaded);
2183            }
2184        });
2185        //call languagesLoaded in case language has been loaded manually
2186        //and remoteLoading is on (onload won't fire)
2187        languagesLoaded();
2188    }
2189
2190    /**
2191     * @method Hyphenator~toggleBox
2192     * @desc
2193     * Creates the toggleBox: a small button to turn off/on hyphenation on a page.
2194     * @see {@link Hyphenator.config}
2195     * @access private
2196     */
2197    var toggleBox = function () {
2198        var bdy,
2199            myTextNode,
2200            text = (Hyphenator.doHyphenation
2201                ? 'Hy-phen-a-tion'
2202                : 'Hyphenation'),
2203            myBox = contextWindow.document.getElementById('HyphenatorToggleBox');
2204        if (!!myBox) {
2205            myBox.firstChild.data = text;
2206        } else {
2207            bdy = contextWindow.document.getElementsByTagName('body')[0];
2208            myBox = createElem('div', contextWindow);
2209            myBox.setAttribute('id', 'HyphenatorToggleBox');
2210            myBox.setAttribute('class', dontHyphenateClass);
2211            myTextNode = contextWindow.document.createTextNode(text);
2212            myBox.appendChild(myTextNode);
2213            myBox.onclick = Hyphenator.toggleHyphenation;
2214            myBox.style.position = 'absolute';
2215            myBox.style.top = '0px';
2216            myBox.style.right = '0px';
2217            myBox.style.zIndex = '1000';
2218            myBox.style.margin = '0';
2219            myBox.style.backgroundColor = '#AAAAAA';
2220            myBox.style.color = '#FFFFFF';
2221            myBox.style.font = '6pt Arial';
2222            myBox.style.letterSpacing = '0.2em';
2223            myBox.style.padding = '3px';
2224            myBox.style.cursor = 'pointer';
2225            myBox.style.WebkitBorderBottomLeftRadius = '4px';
2226            myBox.style.MozBorderRadiusBottomleft = '4px';
2227            myBox.style.borderBottomLeftRadius = '4px';
2228            bdy.appendChild(myBox);
2229        }
2230    };
2231
2232    /**
2233     * @method Hyphenator~doCharSubst
2234     * @desc
2235     * Replace chars in a word
2236     *
2237     * @param {Object} loCharSubst Map of substitutions ({'ä': 'a', 'ü': 'u', …})
2238     * @param {string} w the word
2239     * @returns string The word with substituted characers
2240     * @access private
2241     */
2242    function doCharSubst(loCharSubst, w) {
2243        var r = w;
2244        forEachKey(loCharSubst, function (subst) {
2245            r = r.replace(new RegExp(subst, 'g'), loCharSubst[subst]);
2246        });
2247        return r;
2248    }
2249
2250    /**
2251     * @member {Array} Hyphenator~wwAsMappedCharCodeStore
2252     * @desc
2253     * Array (typed if supported) container for charCodes
2254     * @access private
2255     * @see {@link Hyphenator~hyphenateWord}
2256     */
2257    var wwAsMappedCharCodeStore = (function () {
2258        if (Object.prototype.hasOwnProperty.call(window, "Int32Array")) {
2259            return new window.Int32Array(64);
2260        }
2261        return [];
2262    }());
2263
2264    /**
2265     * @member {Array} Hyphenator~wwhpStore
2266     * @desc
2267     * Array (typed if supported) container for hyphenation points
2268     * @access private
2269     * @see {@link Hyphenator~hyphenateWord}
2270     */
2271    var wwhpStore = (function () {
2272        var r;
2273        if (Object.prototype.hasOwnProperty.call(window, "Uint8Array")) {
2274            r = new window.Uint8Array(64);
2275        } else {
2276            r = [];
2277        }
2278        return r;
2279    }());
2280
2281    /**
2282     * @method Hyphenator~hyphenateWord
2283     * @desc
2284     * This function is the heart of Hyphenator.js. It returns a hyphenated word.
2285     *
2286     * If there's already a {@link Hyphenator~hypen} in the word, the word is returned as it is.
2287     * If the word is in the exceptions list or in the cache, it is retrieved from it.
2288     * If there's a '-' hyphenate the parts.
2289     * The hyphenated word is returned and (if acivated) cached.
2290     * Both special Events onBeforeWordHyphenation and onAfterWordHyphenation are called for the word.
2291     * @param {Object} lo A language object (containing the patterns)
2292     * @param {string} lang The language of the word
2293     * @param {string} word The word
2294     * @returns string The hyphenated word
2295     * @access private
2296     */
2297    function hyphenateWord(lo, lang, word) {
2298        var parts,
2299            i = 0,
2300            pattern = "",
2301            ww,
2302            wwlen,
2303            wwhp = wwhpStore,
2304            pstart = 0,
2305            plen,
2306            hp,
2307            wordLength = word.length,
2308            hw = '',
2309            charMap = lo.charMap.code2int,
2310            charCode,
2311            mappedCharCode,
2312            row = 0,
2313            link = 0,
2314            value = 0,
2315            values,
2316            indexedTrie = lo.indexedTrie,
2317            valueStore = lo.valueStore.keys,
2318            wwAsMappedCharCode = wwAsMappedCharCodeStore;
2319        word = onBeforeWordHyphenation(word, lang);
2320        if (word === '') {
2321            hw = '';
2322        } else if (enableCache && lo.cache && lo.cache.hasOwnProperty(word)) { //the word is in the cache
2323            hw = lo.cache[word];
2324        } else if (word.indexOf(hyphen) !== -1) {
2325            //word already contains shy; -> leave at it is!
2326            hw = word;
2327        } else if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list
2328            hw = lo.exceptions[word].replace(/-/g, hyphen);
2329        } else if (word.indexOf('-') !== -1) {
2330            //word contains '-' -> hyphenate the parts separated with '-'
2331            parts = word.split('-');
2332            while (i < parts.length) {
2333                parts[i] = hyphenateWord(lo, lang, parts[i]);
2334                i += 1;
2335            }
2336            hw = parts.join('-');
2337        } else {
2338            ww = word.toLowerCase();
2339            if (String.prototype.normalize) {
2340                ww = ww.normalize("NFC");
2341            }
2342            if (lo.hasOwnProperty("charSubstitution")) {
2343                ww = doCharSubst(lo.charSubstitution, ww);
2344            }
2345            if (word.indexOf("'") !== -1) {
2346                ww = ww.replace(/'/g, "’");
2346 //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns)
2347            }
2348            ww = '_' + ww + '_';
2349            wwlen = ww.length;
2350            //prepare wwhp and wwAsMappedCharCode
2351            while (pstart < wwlen) {
2352                wwhp[pstart] = 0;
2353                charCode = ww.charCodeAt(pstart);
2354                wwAsMappedCharCode[pstart] = charMap.hasOwnProperty(charCode)
2355                    ? charMap[charCode]
2356                    : -1;
2357                pstart += 1;
2358            }
2359            //get hyphenation points for all substrings
2360            pstart = 0;
2361            while (pstart < wwlen) {
2362                row = 0;
2363                pattern = '';
2364                plen = pstart;
2365                while (plen < wwlen) {
2366                    mappedCharCode = wwAsMappedCharCode[plen];
2367                    if (mappedCharCode === -1) {
2368                        break;
2369                    }
2370                    if (enableReducedPatternSet) {
2371                        pattern += ww.charAt(plen);
2372                    }
2373                    link = indexedTrie[row + mappedCharCode * 2];
2374                    value = indexedTrie[row + mappedCharCode * 2 + 1];
2375                    if (value > 0) {
2376                        hp = valueStore[value];
2377                        while (hp) {
2378                            hp -= 1;
2379                            if (valueStore[value + 1 + hp] > wwhp[pstart + hp]) {
2380                                wwhp[pstart + hp] = valueStore[value + 1 + hp];
2381                            }
2382                        }
2383                        if (enableReducedPatternSet) {
2384                            if (!lo.redPatSet) {
2385                                lo.redPatSet = {};
2386                            }
2387                            if (valueStore.subarray) {
2388                                values = valueStore.subarray(value + 1, value + 1 + valueStore[value]);
2389                            } else {
2390                                values = valueStore.slice(value + 1, value + 1 + valueStore[value]);
2391                            }
2392                            lo.redPatSet[pattern] = recreatePattern(pattern, values);
2393                        }
2394                    }
2395                    if (link > 0) {
2396                        row = link;
2397                    } else {
2398                        break;
2399                    }
2400                    plen += 1;
2401                }
2402                pstart += 1;
2403            }
2404            //create hyphenated word
2405            hp = 0;
2406            while (hp < wordLength) {
2407                if (hp >= lo.leftmin && hp <= (wordLength - lo.rightmin) && (wwhp[hp + 1] % 2) !== 0) {
2408                    hw += hyphen + word.charAt(hp);
2409                } else {
2410                    hw += word.charAt(hp);
2411                }
2412                hp += 1;
2413            }
2414        }
2415        hw = onAfterWordHyphenation(hw, lang);
2416        if (enableCache) { //put the word in the cache
2417            lo.cache[word] = hw;
2418        }
2419        return hw;
2420    }
2421
2422    /**
2423     * @method Hyphenator~removeHyphenationFromElement
2424     * @desc
2425     * Removes all hyphens from the element. If there are other elements, the function is
2426     * called recursively.
2427     * Removing hyphens is usefull if you like to copy text. Some browsers are buggy when the copy hyphenated texts.
2428     * @param {Object} el The element where to remove hyphenation.
2429     * @access public
2430     */
2431    function removeHyphenationFromElement(el) {
2432        var h, u, i = 0, n;
2433        switch (hyphen) {
2434        case '|':
2435            h = '\\|';
2436            break;
2437        case '+':
2438            h = '\\+';
2439            break;
2440        case '*':
2441            h = '\\*';
2442            break;
2443        default:
2444            h = hyphen;
2445        }
2446        switch (urlhyphen) {
2447        case '|':
2448            u = '\\|';
2449            break;
2450        case '+':
2451            u = '\\+';
2452            break;
2453        case '*':
2454            u = '\\*';
2455            break;
2456        default:
2457            u = urlhyphen;
2458        }
2459        n = el.childNodes[i];
2460        while (!!n) {
2461            if (n.nodeType === 3) {
2462                n.data = n.data.replace(new RegExp(h, 'g'), '');
2463                n.data = n.data.replace(new RegExp(u, 'g'), '');
2464            } else if (n.nodeType === 1) {
2465                removeHyphenationFromElement(n);
2466            }
2467            i += 1;
2468            n = el.childNodes[i];
2469        }
2470    }
2471
2472    var copy = (function () {
2473        var makeCopy = function () {
2474            var oncopyHandler = function (e) {
2475                    e = e || window.event;
2476                    var shadow,
2477                        selection,
2478                        range,
2479                        rangeShadow,
2480                        restore,
2481                        target = e.target || e.srcElement,
2482                        currDoc = target.ownerDocument,
2483                        bdy = currDoc.getElementsByTagName('body')[0],
2484                        targetWindow = currDoc.defaultView || currDoc.parentWindow;
2485                    if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) {
2486                        //Safari needs this
2487                        return;
2488                    }
2489                    //create a hidden shadow element
2490                    shadow = currDoc.createElement('div');
2491                    //Moving the element out of the screen doesn't work for IE9 (https://connect.microsoft.com/IE/feedback/details/663981/)
2492                    //shadow.style.overflow = 'hidden';
2493                    //shadow.style.position = 'absolute';
2494                    //shadow.style.top = '-5000px';
2495                    //shadow.style.height = '1px';
2496                    //doing this instead:
2497                    shadow.style.color = window.getComputedStyle
2498                        ? targetWindow.getComputedStyle(bdy, null).backgroundColor
2499                        : '#FFFFFF';
2500                    shadow.style.fontSize = '0px';
2501                    bdy.appendChild(shadow);
2502                    if (!!window.getSelection) {
2503                        //FF3, Webkit, IE9
2504                        e.stopPropagation();
2505                        selection = targetWindow.getSelection();
2506                        range = selection.getRangeAt(0);
2507                        shadow.appendChild(range.cloneContents());
2508                        removeHyphenationFromElement(shadow);
2509                        selection.selectAllChildren(shadow);
2510                        restore = function () {
2511                            shadow.parentNode.removeChild(shadow);
2512                            selection.removeAllRanges(); //IE9 needs that
2513                            selection.addRange(range);
2514                        };
2515                    } else {
2516                        // IE<9
2517                        e.cancelBubble = true;
2518                        selection = targetWindow.document.selection;
2519                        range = selection.createRange();
2520                        shadow.innerHTML = range.htmlText;
2521                        removeHyphenationFromElement(shadow);
2522                        rangeShadow = bdy.createTextRange();
2523                        rangeShadow.moveToElementText(shadow);
2524                        rangeShadow.select();
2525                        restore = function () {
2526                            shadow.parentNode.removeChild(shadow);
2527                            if (range.text !== "") {
2528                                range.select();
2529                            }
2530                        };
2531                    }
2532                    zeroTimeOut(restore);
2533                },
2534                removeOnCopy = function (el) {
2535                    var body = el.ownerDocument.getElementsByTagName('body')[0];
2536                    if (!body) {
2537                        return;
2538                    }
2539                    el = el || body;
2540                    if (window.removeEventListener) {
2541                        el.removeEventListener("copy", oncopyHandler, true);
2542                    } else {
2543                        el.detachEvent("oncopy", oncopyHandler);
2544                    }
2545                },
2546                registerOnCopy = function (el) {
2547                    var body = el.ownerDocument.getElementsByTagName('body')[0];
2548                    if (!body) {
2549                        return;
2550                    }
2551                    el = el || body;
2552                    if (window.addEventListener) {
2553                        el.addEventListener("copy", oncopyHandler, true);
2554                    } else {
2555                        el.attachEvent("oncopy", oncopyHandler);
2556                    }
2557                };
2558            return {
2559                oncopyHandler: oncopyHandler,
2560                removeOnCopy: removeOnCopy,
2561                registerOnCopy: registerOnCopy
2562            };
2563        };
2564
2565        return (safeCopy
2566            ? makeCopy()
2567            : false);
2568    }());
2569
2570
2571    /**
2572     * @method Hyphenator~checkIfAllDone
2573     * @desc
2574     * Checks if all elements in {@link Hyphenator~elements} are hyphenated, unhides them and fires onHyphenationDone()
2575     * @access private
2576     */
2577    function checkIfAllDone() {
2578        var allDone = true,
2579            i = 0,
2580            doclist = {};
2581        elements.each(function (ellist) {
2582            var j = 0,
2583                l = ellist.length;
2584            while (j < l) {
2585                allDone = allDone && ellist[j].hyphenated;
2586                if (!doclist.hasOwnProperty(ellist[j].element.baseURI)) {
2587                    doclist[ellist[j].element.ownerDocument.location.href] = true;
2588                }
2589                doclist[ellist[j].element.ownerDocument.location.href] = doclist[ellist[j].element.ownerDocument.location.href] && ellist[j].hyphenated;
2590                j += 1;
2591            }
2592        });
2593        if (allDone) {
2594            if (intermediateState === 'hidden' && unhide === 'progressive') {
2595                elements.each(function (ellist) {
2596                    var j = 0,
2597                        l = ellist.length,
2598                        el;
2599                    while (j < l) {
2600                        el = ellist[j].element;
2601                        el.className = el.className.replace(unhideClassRegExp, '');
2602                        if (el.className === '') {
2603                            el.removeAttribute('class');
2604                        }
2605                        j += 1;
2606                    }
2607                });
2608            }
2609            while (i < CSSEditors.length) {
2610                CSSEditors[i].clearChanges();
2611                i += 1;
2612            }
2613            forEachKey(doclist, function (doc) {
2614                onHyphenationDone(doc);
2615            });
2616            if (!!storage && storage.deferred.length > 0) {
2617                i = 0;
2618                while (i < storage.deferred.length) {
2619                    storage.deferred[i].call();
2620                    i += 1;
2621                }
2622                storage.deferred = [];
2623            }
2624        }
2625    }
2626
2627    /**
2628     * @method Hyphenator~controlOrphans
2629     * @desc
2630     * removes orphans depending on the 'orphanControl'-setting:
2631     * orphanControl === 1: do nothing
2632     * orphanControl === 2: prevent last word to be hyphenated
2633     * orphanControl === 3: prevent one word on a last line (inserts a nobreaking space)
2634     * @param {string} part - The sring where orphans have to be removed
2635     * @access private
2636     */
2637    function controlOrphans(part) {
2638        var h, r;
2639        switch (hyphen) {
2640        case '|':
2641            h = '\\|';
2642            break;
2643        case '+':
2644            h = '\\+';
2645            break;
2646        case '*':
2647            h = '\\*';
2648            break;
2649        default:
2650            h = hyphen;
2651        }
2652        //strip off blank space at the end (omitted closing tags)
2653        part = part.replace(/[\s]*$/, '');
2654        if (orphanControl >= 2) {
2655            //remove hyphen points from last word
2656            r = part.split(' ');
2657            r[1] = r[1].replace(new RegExp(h, 'g'), '');
2658            r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), '');
2659            r = r.join(' ');
2660        }
2661        if (orphanControl === 3) {
2662            //replace spaces by non breaking spaces
2663            r = r.replace(/[\ ]+/g, String.fromCharCode(160));
2664        }
2665        return r;
2666    }
2667
2668    /**
2669     * @method Hyphenator~hyphenateElement
2670     * @desc
2671     * Takes the content of the given element and - if there's text - replaces the words
2672     * by hyphenated words. If there's another element, the function is called recursively.
2673     * When all words are hyphenated, the visibility of the element is set to 'visible'.
2674     * @param {string} lang - The language-code of the element
2675     * @param {Element} elo - The element to hyphenate {@link Hyphenator~elements~ElementCollection~Element}
2676     * @access private
2677     */
2678    function hyphenateElement(lang, elo) {
2679        var el = elo.element,
2680            hyphenate,
2681            n,
2682            i,
2683            lo;
2684        if (Hyphenator.languages.hasOwnProperty(lang) && Hyphenator.doHyphenation) {
2685            lo = Hyphenator.languages[lang];
2686            hyphenate = function (match, word, url, mail) {
2687                var r;
2688                if (!!url || !!mail) {
2689                    r = hyphenateURL(match);
2690                } else {
2691                    r = hyphenateWord(lo, lang, word);
2692                }
2693                return r;
2694            };
2695            if (safeCopy && (el.tagName.toLowerCase() !== 'body')) {
2696                copy.registerOnCopy(el);
2697            }
2698            i = 0;
2699            n = el.childNodes[i];
2700            while (!!n) {
2701                if (n.nodeType === 3 //type 3 = #text
2702                        && (/\S/).test(n.data) //not just white space
2703                        && n.data.length >= min) { //longer then min
2704                    n.data = n.data.replace(lo.genRegExp, hyphenate);
2705                    if (orphanControl !== 1) {
2706                        n.data = n.data.replace(/[\S]+\ [\S]+[\s]*$/, controlOrphans);
2707                    }
2708                }
2709                i += 1;
2710                n = el.childNodes[i];
2711            }
2712        }
2713        if (intermediateState === 'hidden' && unhide === 'wait') {
2714            el.className = el.className.replace(hideClassRegExp, '');
2715            if (el.className === '') {
2716                el.removeAttribute('class');
2717            }
2718        }
2719        if (intermediateState === 'hidden' && unhide === 'progressive') {
2720            el.className = el.className.replace(hideClassRegExp, ' ' + unhideClass);
2721        }
2722        elo.hyphenated = true;
2723        elements.counters[1] += 1;
2724        if (elements.counters[0] <= elements.counters[1]) {
2725            checkIfAllDone();
2726        }
2727    }
2728
2729    /**
2730     * @method Hyphenator~hyphenateLanguageElements
2731     * @desc
2732     * Calls hyphenateElement() for all elements of the specified language.
2733     * If the language is '*' then all elements are hyphenated.
2734     * This is done with a setTimout
2735     * to prevent a "long running Script"-alert when hyphenating large pages.
2736     * Therefore a tricky bind()-function was necessary.
2737     * @param {string} lang The language of the elements to hyphenate
2738     * @access private
2739     */
2740
2741    function hyphenateLanguageElements(lang) {
2742        /*function bind(fun, arg1, arg2) {
2743            return function () {
2744                return fun(arg1, arg2);
2745            };
2746        }*/
2747        var i = 0,
2748            l;
2749        if (lang === '*') {
2750            elements.each(function (lang, ellist) {
2751                var j = 0,
2752                    le = ellist.length;
2753                while (j < le) {
2754                    //zeroTimeOut(bind(hyphenateElement, lang, ellist[j]));
2755                    hyphenateElement(lang, ellist[j]);
2756                    j += 1;
2757                }
2758            });
2759        } else {
2760            if (elements.list.hasOwnProperty(lang)) {
2761                l = elements.list[lang].length;
2762                while (i < l) {
2763                    //zeroTimeOut(bind(hyphenateElement, lang, elements.list[lang][i]));
2764                    hyphenateElement(lang, elements.list[lang][i]);
2765                    i += 1;
2766                }
2767            }
2768        }
2769    }
2770
2771    /**
2772     * @method Hyphenator~removeHyphenationFromDocument
2773     * @desc
2774     * Does what it says and unregisters the onCopyEvent from the elements
2775     * @access private
2776     */
2777    function removeHyphenationFromDocument() {
2778        elements.each(function (ellist) {
2779            var i = 0,
2780                l = ellist.length;
2781            while (i < l) {
2782                removeHyphenationFromElement(ellist[i].element);
2783                if (safeCopy) {
2784                    copy.removeOnCopy(ellist[i].element);
2785                }
2786                ellist[i].hyphenated = false;
2787                i += 1;
2788            }
2789        });
2790    }
2791
2792    /**
2793     * @method Hyphenator~createStorage
2794     * @desc
2795     * inits the private var {@link Hyphenator~storage) depending of the setting in {@link Hyphenator~storageType}
2796     * and the supported features of the system.
2797     * @access private
2798     */
2799    function createStorage() {
2800        var s;
2801        function makeStorage(s) {
2802            var store = s,
2803                prefix = 'Hyphenator_' + Hyphenator.version + '_',
2804                deferred = [],
2805                test = function (name) {
2806                    var val = store.getItem(prefix + name);
2807                    return !!val;
2808                },
2809                getItem = function (name) {
2810                    return store.getItem(prefix + name);
2811                },
2812                setItem = function (name, value) {
2813                    try {
2814                        store.setItem(prefix + name, value);
2815                    } catch (e) {
2816                        onError(e);
2817                    }
2818                };
2819            return {
2820                deferred: deferred,
2821                test: test,
2822                getItem: getItem,
2823                setItem: setItem
2824            };
2825        }
2826        try {
2827            if (storageType !== 'none' &&
2828                    window.JSON !== undefined &&
2829                    window.localStorage !== undefined &&
2830                    window.sessionStorage !== undefined &&
2831                    window.JSON.stringify !== undefined &&
2832                    window.JSON.parse !== undefined) {
2833                switch (storageType) {
2834                case 'session':
2835                    s = window.sessionStorage;
2836                    break;
2837                case 'local':
2838                    s = window.localStorage;
2839                    break;
2840                default:
2841                    s = undefined;
2842                    break;
2843                }
2844                //check for private mode
2845                s.setItem('storageTest', '1');
2846                s.removeItem('storageTest');
2847            }
2848        } catch (ignore) {
2849            //FF throws an error if DOM.storage.enabled is set to false
2850            s = undefined;
2851        }
2852        if (s) {
2853            storage = makeStorage(s);
2854        } else {
2855            storage = undefined;
2856        }
2857    }
2858
2859    /**
2860     * @method Hyphenator~storeConfiguration
2861     * @desc
2862     * Stores the current config-options in DOM-Storage
2863     * @access private
2864     */
2865    function storeConfiguration() {
2866        if (!storage) {
2867            return;
2868        }
2869        var settings = {
2870            'STORED': true,
2871            'classname': hyphenateClass,
2872            'urlclassname': urlHyphenateClass,
2873            'donthyphenateclassname': dontHyphenateClass,
2874            'minwordlength': min,
2875            'hyphenchar': hyphen,
2876            'urlhyphenchar': urlhyphen,
2877            'togglebox': toggleBox,
2878            'displaytogglebox': displayToggleBox,
2879            'remoteloading': enableRemoteLoading,
2880            'enablecache': enableCache,
2881            'enablereducedpatternset': enableReducedPatternSet,
2882            'onhyphenationdonecallback': onHyphenationDone,
2883            'onerrorhandler': onError,
2884            'onwarninghandler': onWarning,
2885            'intermediatestate': intermediateState,
2886            'selectorfunction': selectorFunction || mySelectorFunction,
2887            'safecopy': safeCopy,
2888            'doframes': doFrames,
2889            'storagetype': storageType,
2890            'orphancontrol': orphanControl,
2891            'dohyphenation': Hyphenator.doHyphenation,
2892            'persistentconfig': persistentConfig,
2893            'defaultlanguage': defaultLanguage,
2894            'useCSS3hyphenation': css3,
2895            'unhide': unhide,
2896            'onbeforewordhyphenation': onBeforeWordHyphenation,
2897            'onafterwordhyphenation': onAfterWordHyphenation
2898        };
2899        storage.setItem('config', window.JSON.stringify(settings));
2900    }
2901
2902    /**
2903     * @method Hyphenator~restoreConfiguration
2904     * @desc
2905     * Retrieves config-options from DOM-Storage and does configuration accordingly
2906     * @access private
2907     */
2908    function restoreConfiguration() {
2909        var settings;
2910        if (storage.test('config')) {
2911            settings = window.JSON.parse(storage.getItem('config'));
2912            Hyphenator.config(settings);
2913        }
2914    }
2915
2916    return {
2917
2918        /**
2919         * @member {string} Hyphenator.version
2920         * @desc
2921         * String containing the actual version of Hyphenator.js
2922         * [major release].[minor releas].[bugfix release]
2923         * major release: new API, new Features, big changes
2924         * minor release: new languages, improvements
2925         * @access public
2926         */
2927        version: '5.2.0(devel)',
2928
2929        /**
2930         * @member {boolean} Hyphenator.doHyphenation
2931         * @desc
2932         * If doHyphenation is set to false, hyphenateDocument() isn't called.
2933         * All other actions are performed.
2934         * @default true
2935         */
2936        doHyphenation: true,
2937
2938        /**
2939         * @typedef {Object} Hyphenator.languages.language
2940         * @property {Number} leftmin - The minimum of chars to remain on the old line
2941         * @property {Number} rightmin - The minimum of chars to go on the new line
2942         * @property {string} specialChars - Non-ASCII chars in the alphabet.
2943         * @property {Object.<number, string>} patterns - the patterns in a compressed format. The key is the length of the patterns in the value string.
2944         * @property {Object.<string, string>} charSubstitution - optional: a hash table with chars that are replaced during hyphenation
2945         * @property {string | Object.<string, string>} exceptions - optional: a csv string containing exceptions
2946         */
2947
2948        /**
2949         * @member {Object.<string, Hyphenator.languages.language>} Hyphenator.languages
2950         * @desc
2951         * Objects that holds key-value pairs, where key is the language and the value is the
2952         * language-object loaded from (and set by) the pattern file.
2953         * @namespace Hyphenator.languages
2954         * @access public
2955         */
2956        languages: {},
2957
2958
2959        /**
2960         * @method Hyphenator.config
2961         * @desc
2962         * The Hyphenator.config() function that takes an object as an argument. The object contains key-value-pairs
2963         * containig Hyphenator-settings.
2964         * @param {Hyphenator.config} obj
2965         * @access public
2966         * @example
2967         * &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
2968         * &lt;script type = "text/javascript"&gt;
2969         *     Hyphenator.config({'minwordlength':4,'hyphenchar':'|'});
2970         *     Hyphenator.run();
2971         * &lt;/script&gt;
2972         */
2973        config: function (obj) {
2974            var assert = function (name, type) {
2975                var r,
2976                    t;
2977                t = typeof obj[name];
2978                if (t === type) {
2979                    r = true;
2980                } else {
2981                    onError(new Error('Config onError: ' + name + ' must be of type ' + type));
2982                    r = false;
2983                }
2984                return r;
2985            };
2986
2987            if (obj.hasOwnProperty('storagetype')) {
2988                if (assert('storagetype', 'string')) {
2989                    storageType = obj.storagetype;
2990                }
2991                if (!storage) {
2992                    createStorage();
2993                }
2994            }
2995            if (!obj.hasOwnProperty('STORED') && storage && obj.hasOwnProperty('persistentconfig') && obj.persistentconfig === true) {
2996                restoreConfiguration();
2997            }
2998
2999            forEachKey(obj, function (key) {
3000                switch (key) {
3001                case 'STORED':
3002                    break;
3003                case 'classname':
3004                    if (assert('classname', 'string')) {
3005                        hyphenateClass = obj[key];
3006                    }
3007                    break;
3008                case 'urlclassname':
3009                    if (assert('urlclassname', 'string')) {
3010                        urlHyphenateClass = obj[key];
3011                    }
3012                    break;
3013                case 'donthyphenateclassname':
3014                    if (assert('donthyphenateclassname', 'string')) {
3015                        dontHyphenateClass = obj[key];
3016                    }
3017                    break;
3018                case 'minwordlength':
3019                    if (assert('minwordlength', 'number')) {
3020                        min = obj[key];
3021                    }
3022                    break;
3023                case 'hyphenchar':
3024                    if (assert('hyphenchar', 'string')) {
3025                        if (obj.hyphenchar === '&shy;') {
3026                            obj.hyphenchar = String.fromCharCode(173);
3027                        }
3028                        hyphen = obj[key];
3029                    }
3030                    break;
3031                case 'urlhyphenchar':
3032                    if (obj.hasOwnProperty('urlhyphenchar')) {
3033                        if (assert('urlhyphenchar', 'string')) {
3034                            urlhyphen = obj[key];
3035                        }
3036                    }
3037                    break;
3038                case 'togglebox':
3039                    if (assert('togglebox', 'function')) {
3040                        toggleBox = obj[key];
3041                    }
3042                    break;
3043                case 'displaytogglebox':
3044                    if (assert('displaytogglebox', 'boolean')) {
3045                        displayToggleBox = obj[key];
3046                    }
3047                    break;
3048                case 'remoteloading':
3049                    if (assert('remoteloading', 'boolean')) {
3050                        enableRemoteLoading = obj[key];
3051                    }
3052                    break;
3053                case 'enablecache':
3054                    if (assert('enablecache', 'boolean')) {
3055                        enableCache = obj[key];
3056                    }
3057                    break;
3058                case 'enablereducedpatternset':
3059                    if (assert('enablereducedpatternset', 'boolean')) {
3060                        enableReducedPatternSet = obj[key];
3061                    }
3062                    break;
3063                case 'onhyphenationdonecallback':
3064                    if (assert('onhyphenationdonecallback', 'function')) {
3065                        onHyphenationDone = obj[key];
3066                    }
3067                    break;
3068                case 'onerrorhandler':
3069                    if (assert('onerrorhandler', 'function')) {
3070                        onError = obj[key];
3071                    }
3072                    break;
3073                case 'onwarninghandler':
3074                    if (assert('onwarninghandler', 'function')) {
3075                        onWarning = obj[key];
3076                    }
3077                    break;
3078                case 'intermediatestate':
3079                    if (assert('intermediatestate', 'string')) {
3080                        intermediateState = obj[key];
3081                    }
3082                    break;
3083                case 'selectorfunction':
3084                    if (assert('selectorfunction', 'function')) {
3085                        selectorFunction = obj[key];
3086                    }
3087                    break;
3088                case 'safecopy':
3089                    if (assert('safecopy', 'boolean')) {
3090                        safeCopy = obj[key];
3091                    }
3092                    break;
3093                case 'doframes':
3094                    if (assert('doframes', 'boolean')) {
3095                        doFrames = obj[key];
3096                    }
3097                    break;
3098                case 'storagetype':
3099                    if (assert('storagetype', 'string')) {
3100                        storageType = obj[key];
3101                    }
3102                    break;
3103                case 'orphancontrol':
3104                    if (assert('orphancontrol', 'number')) {
3105                        orphanControl = obj[key];
3106                    }
3107                    break;
3108                case 'dohyphenation':
3109                    if (assert('dohyphenation', 'boolean')) {
3110                        Hyphenator.doHyphenation = obj[key];
3111                    }
3112                    break;
3113                case 'persistentconfig':
3114                    if (assert('persistentconfig', 'boolean')) {
3115                        persistentConfig = obj[key];
3116                    }
3117                    break;
3118                case 'defaultlanguage':
3119                    if (assert('defaultlanguage', 'string')) {
3120                        defaultLanguage = obj[key];
3121                    }
3122                    break;
3123                case 'useCSS3hyphenation':
3124                    if (assert('useCSS3hyphenation', 'boolean')) {
3125                        css3 = obj[key];
3126                    }
3127                    break;
3128                case 'unhide':
3129                    if (assert('unhide', 'string')) {
3130                        unhide = obj[key];
3131                    }
3132                    break;
3133                case 'onbeforewordhyphenation':
3134                    if (assert('onbeforewordhyphenation', 'function')) {
3135                        onBeforeWordHyphenation = obj[key];
3136                    }
3137                    break;
3138                case 'onafterwordhyphenation':
3139                    if (assert('onafterwordhyphenation', 'function')) {
3140                        onAfterWordHyphenation = obj[key];
3141                    }
3142                    break;
3143                default:
3144                    onError(new Error('Hyphenator.config: property ' + key + ' not known.'));
3145                }
3146            });
3147            if (storage && persistentConfig) {
3148                storeConfiguration();
3149            }
3150        },
3151
3152        /**
3153         * @method Hyphenator.run
3154         * @desc
3155         * Bootstrap function that starts all hyphenation processes when called:
3156         * Tries to create storage if required and calls {@link Hyphenator~runWhenLoaded} on 'window' handing over the callback 'process'
3157         * @access public
3158         * @example
3159         * &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
3160         * &lt;script type = "text/javascript"&gt;
3161         *   Hyphenator.run();
3162         * &lt;/script&gt;
3163         */
3164        run: function () {
3165                /**
3166                 *@callback Hyphenator.run~process process - The function is called when the DOM has loaded (or called for each frame)
3167                 */
3168            var process = function () {
3169                try {
3170                    if (contextWindow.document.getElementsByTagName('frameset').length > 0) {
3171                        return; //we are in a frameset
3172                    }
3173                    autoSetMainLanguage(undefined);
3174                    gatherDocumentInfos();
3175                    if (displayToggleBox) {
3176                        toggleBox();
3177                    }
3178                    prepare(hyphenateLanguageElements);
3179                } catch (e) {
3180                    onError(e);
3181                }
3182            };
3183
3184            if (!storage) {
3185                createStorage();
3186            }
3187            runWhenLoaded(window, process);
3188        },
3189
3190        /**
3191         * @method Hyphenator.addExceptions
3192             * @desc
3193         * Adds the exceptions from the string to the appropriate language in the
3194         * {@link Hyphenator~languages}-object
3195         * @param {string} lang The language
3196         * @param {string} words A comma separated string of hyphenated words WITH spaces.
3197         * @access public
3198         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
3199         * &lt;script type = "text/javascript"&gt;
3200         *   Hyphenator.addExceptions('de','ziem-lich, Wach-stube');
3201         *   Hyphenator.run();
3202         * &lt;/script&gt;
3203         */
3204        addExceptions: function (lang, words) {
3205            if (lang === '') {
3206                lang = 'global';
3207            }
3208            if (exceptions.hasOwnProperty(lang)) {
3209                exceptions[lang] += ", " + words;
3210            } else {
3211                exceptions[lang] = words;
3212            }
3213        },
3214
3215        /**
3216         * @method Hyphenator.hyphenate
3217         * @access public
3218         * @desc
3219         * Hyphenates the target. The language patterns must be loaded.
3220         * If the target is a string, the hyphenated string is returned,
3221         * if it's an object, the values are hyphenated directly and undefined (aka nothing) is returned
3222         * @param {string|Object} target the target to be hyphenated
3223         * @param {string} lang the language of the target
3224         * @returns {string|undefined}
3225         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
3226         * &lt;script src = "patterns/en.js" type = "text/javascript"&gt;&lt;/script&gt;
3227         * &lt;script type = "text/javascript"&gt;
3228         * var t = Hyphenator.hyphenate('Hyphenation', 'en'); //Hy|phen|ation
3229         * &lt;/script&gt;
3230         */
3231        hyphenate: function (target, lang) {
3232            var hyphenate, n, i, lo;
3233            lo = Hyphenator.languages[lang];
3234            if (Hyphenator.languages.hasOwnProperty(lang)) {
3235                if (!lo.prepared) {
3236                    prepareLanguagesObj(lang);
3237                }
3238                hyphenate = function (match, word, url, mail) {
3239                    var r;
3240                    if (!!url || !!mail) {
3241                        r = hyphenateURL(match);
3242                    } else {
3243                        r = hyphenateWord(lo, lang, word);
3244                    }
3245                    return r;
3246                };
3247                if (typeof target === 'object' && !(typeof target === 'string' || target.constructor === String)) {
3248                    i = 0;
3249                    n = target.childNodes[i];
3250                    while (!!n) {
3251                        if (n.nodeType === 3 //type 3 = #text
3252                                && (/\S/).test(n.data) //not just white space
3253                                && n.data.length >= min) { //longer then min
3254                            n.data = n.data.replace(lo.genRegExp, hyphenate);
3255                        } else if (n.nodeType === 1) {
3256                            if (n.lang !== '') {
3257                                Hyphenator.hyphenate(n, n.lang);
3258                            } else {
3259                                Hyphenator.hyphenate(n, lang);
3260                            }
3261                        }
3262                        i += 1;
3263                        n = target.childNodes[i];
3264                    }
3265                } else if (typeof target === 'string' || target.constructor === String) {
3266                    return target.replace(lo.genRegExp, hyphenate);
3267                }
3268            } else {
3269                onError(new Error('Language "' + lang + '" is not loaded.'));
3270            }
3271        },
3272
3273        /**
3274         * @method Hyphenator.getRedPatternSet
3275         * @desc
3276         * Returns the reduced pattern set: an object looking like: {'patk': pat}
3277         * @param {string} lang the language patterns are stored for
3278         * @returns {Object.<string, string>}
3279         * @access public
3280         */
3281        getRedPatternSet: function (lang) {
3282            return Hyphenator.languages[lang].redPatSet;
3283        },
3284
3285        /**
3286         * @method Hyphenator.isBookmarklet
3287         * @desc
3288         * Returns {@link Hyphenator~isBookmarklet}.
3289         * @returns {boolean}
3290         * @access public
3291         */
3292        isBookmarklet: function () {
3293            return isBookmarklet;
3294        },
3295
3296        /**
3297         * @method Hyphenator.getConfigFromURI
3298         * @desc
3299         * reads and sets configurations from GET parameters in the URI
3300         * @access public
3301         */
3302        getConfigFromURI: function () {
3303            var loc = null,
3304                re = {},
3305                jsArray = contextWindow.document.getElementsByTagName('script'),
3306                i = 0,
3307                j = 0,
3308                l = jsArray.length,
3309                s,
3310                gp,
3311                option;
3312            while (i < l) {
3313                if (!!jsArray[i].getAttribute('src')) {
3314                    loc = jsArray[i].getAttribute('src');
3315                }
3316                if (loc && (loc.indexOf('Hyphenator.js?') !== -1)) {
3317                    s = loc.indexOf('Hyphenator.js?');
3318                    gp = loc.substring(s + 14).split('&');
3319                    while (j < gp.length) {
3320                        option = gp[j].split('=');
3321                        if (option[0] !== 'bm') {
3322                            if (option[1] === 'true') {
3323                                option[1] = true;
3324                            } else if (option[1] === 'false') {
3325                                option[1] = false;
3326                            } else if (isFinite(option[1])) {
3327                                option[1] = parseInt(option[1], 10);
3328                            }
3329                            if (option[0] === 'togglebox' ||
3330                                    option[0] === 'onhyphenationdonecallback' ||
3331                                    option[0] === 'onerrorhandler' ||
3332                                    option[0] === 'selectorfunction' ||
3333                                    option[0] === 'onbeforewordhyphenation' ||
3334                                    option[0] === 'onafterwordhyphenation') {
3335                                option[1] = new Function('', option[1]);
3336                            }
3337                            re[option[0]] = option[1];
3338                        }
3339                        j += 1;
3340                    }
3341                    break;
3342                }
3343                i += 1;
3344            }
3345            return re;
3346        },
3347
3348        /**
3349         * @method Hyphenator.toggleHyphenation
3350         * @desc
3351         * Checks the current state of the ToggleBox and removes or does hyphenation.
3352         * @access public
3353         */
3354        toggleHyphenation: function () {
3355            if (Hyphenator.doHyphenation) {
3356                if (!!css3hyphenateClassHandle) {
3357                    css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': none;');
3358                }
3359                removeHyphenationFromDocument();
3360                Hyphenator.doHyphenation = false;
3361                storeConfiguration();
3362                toggleBox();
3363            } else {
3364                if (!!css3hyphenateClassHandle) {
3365                    css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': auto;');
3366                }
3367                Hyphenator.doHyphenation = true;
3368                hyphenateLanguageElements('*');
3369                storeConfiguration();
3370                toggleBox();
3371            }
3372        }
3373    };
3374}(window));
3375
3376//Export properties/methods (for google closure compiler)
3377/**** to be moved to external file
3378Hyphenator['languages'] = Hyphenator.languages;
3379Hyphenator['config'] = Hyphenator.config;
3380Hyphenator['run'] = Hyphenator.run;
3381Hyphenator['addExceptions'] = Hyphenator.addExceptions;
3382Hyphenator['hyphenate'] = Hyphenator.hyphenate;
3383Hyphenator['getRedPatternSet'] = Hyphenator.getRedPatternSet;
3384Hyphenator['isBookmarklet'] = Hyphenator.isBookmarklet;
3385Hyphenator['getConfigFromURI'] = Hyphenator.getConfigFromURI;
3386Hyphenator['toggleHyphenation'] = Hyphenator.toggleHyphenation;
3387window['Hyphenator'] = Hyphenator;
3388*/
3389
3390/*
3391 * call Hyphenator if it is a Bookmarklet
3392 */
3393if (Hyphenator.isBookmarklet()) {
3394    Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', storagetype: 'local', doframes: true, useCSS3hyphenation: true});
3395    Hyphenator.config(Hyphenator.getConfigFromURI());
3396    Hyphenator.run();
3397}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.