﻿/** @license Hyphenator 3.3.0 - client side hyphenation for webbrowsers
 *  Copyright (C) 2011  Mathias Nater, Zürich (mathias at mnn dot ch)
 *  Project and Source hosted on http://code.google.com/p/hyphenator/
 * 
 *  This JavaScript code is free software: you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser
 *  General Public License (GNU LGPL) as published by the Free Software
 *  Foundation, either version 3 of the License, or (at your option)
 *  any later version.  The code is distributed WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 *  As additional permission under GNU GPL version 3 section 7, you
 *  may distribute non-source (e.g., minimized or compacted) forms of
 *  that code without the copy of the GNU GPL normally required by
 *  section 4, provided you include this license notice and a URL
 *  through which recipients can access the Corresponding Source.
 */
 
/* 
 *  Comments are jsdoctoolkit formatted. See http://code.google.com/p/jsdoc-toolkit/
 */
 
/* The following comment is for JSLint: */
/*global window, ActiveXObject, unescape */
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, regexp: true, sub: true, newcap: true, immed: true, evil: true, eqeqeq: false */


/**
 * @constructor
 * @description Provides all functionality to do hyphenation, except the patterns that are loaded
 * externally.
 * @author Mathias Nater, <a href = "mailto:mathias@mnn.ch">mathias@mnn.ch</a>
 * @version 3.3.0
 * @namespace Holds all methods and properties
 * @example
 * &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
 * &lt;script type = "text/javascript"&gt;
 *   Hyphenator.run();
 * &lt;/script&gt;
 */
var Hyphenator = (function (window) {

  var
  /**
   * @name Hyphenator-supportedLang
   * @description
   * A key-value object that stores supported languages.
   * The key is the bcp47 code of the language and the value
   * is the (abbreviated) filename of the pattern file.
   * @type {Object.<string, string>}
   * @private
   * @example
   * Check if language lang is supported:
   * if (supportedLang.hasOwnProperty(lang))
   */
  supportedLang = {
    'be': 'be.js',
    'ca': 'ca.js',
    'cs': 'cs.js',
    'da': 'da.js',
    'bn': 'bn.js',
    'de': 'de.js',
    'el': 'el-monoton.js',
    'el-monoton': 'el-monoton.js',
    'el-polyton': 'el-polyton.js',
    'en': 'en-us.js',
    'en-gb': 'en-gb.js',
    'en-us': 'en-us.js',
    'es': 'es.js',
    'fi': 'fi.js',
    'fr': 'fr.js',
    'grc': 'grc.js',
    'gu': 'gu.js',
    'hi': 'hi.js',
    'hu': 'hu.js',
    'hy': 'hy.js',
    'it': 'it.js',
    'kn': 'kn.js',
    'la': 'la.js',
    'lt': 'lt.js',
    'lv': 'lv.js',
    'ml': 'ml.js',
    'no': 'no-nb.js',
    'no-nb': 'no-nb.js',
    'nl': 'nl.js',
    'or': 'or.js',
    'pa': 'pa.js',
    'pl': 'pl.js',
    'pt': 'pt.js',
    'ru': 'ru.js',
    'sl': 'sl.js',
    'sv': 'sv.js',
    'ta': 'ta.js',
    'te': 'te.js',
    'tr': 'tr.js',
    'uk': 'uk.js'
  },

  /**
   * @name Hyphenator-languageHint
   * @description
   * An automatically generated string to be displayed in a prompt if the language can't be guessed.
   * The string is generated using the supportedLang-object.
   * @see Hyphenator-supportedLang
   * @type {string}
   * @private
   * @see Hyphenator-autoSetMainLanguage
   */

  languageHint = (function () {
    var k, r = '';
    for (k in supportedLang) {
      if (supportedLang.hasOwnProperty(k)) {
        r += k + ', ';
      }
    }
    r = r.substring(0, r.length - 2);
    return r;
  }()),
  
  /**
   * @name Hyphenator-prompterStrings
   * @description
   * A key-value object holding the strings to be displayed if the language can't be guessed
   * If you add hyphenation patterns change this string.
   * @type {Object.<string,string>}
   * @private
   * @see Hyphenator-autoSetMainLanguage
   */  
  prompterStrings = {
    'be': 'Мова гэтага сайта не можа быць вызначаны аўтаматычна. Калі ласка пакажыце мову:',
    'cs': 'Jazyk této internetové stránky nebyl automaticky rozpoznán. Určete prosím její jazyk:',
    'da': 'Denne websides sprog kunne ikke bestemmes. Angiv venligst sprog:',
    'de': 'Die Sprache dieser Webseite konnte nicht automatisch bestimmt werden. Bitte Sprache angeben:',
    'en': 'The language of this website could not be determined automatically. Please indicate the main language:',
    'es': 'El idioma del sitio no pudo determinarse autom%E1ticamente. Por favor, indique el idioma principal:',
    'fi': 'Sivun kielt%E4 ei tunnistettu automaattisesti. M%E4%E4rit%E4 sivun p%E4%E4kieli:',
    'fr': 'La langue de ce site n%u2019a pas pu %EAtre d%E9termin%E9e automatiquement. Veuillez indiquer une langue, s.v.p.%A0:',
    'hu': 'A weboldal nyelvét nem sikerült automatikusan megállapítani. Kérem adja meg a nyelvet:',
    'hy': 'Չհաջողվեց հայտնաբերել այս կայքի լեզուն։ Խնդրում ենք նշեք հիմնական լեզուն՝',
    'it': 'Lingua del sito sconosciuta. Indicare una lingua, per favore:',
    'kn': 'ಜಾಲ ತಾಣದ ಭಾಷೆಯನ್ನು ನಿರ್ಧರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಮುಖ್ಯ ಭಾಷೆಯನ್ನು ಸೂಚಿಸಿ:',
    'lt': 'Nepavyko automatiškai nustatyti šios svetainės kalbos. Prašome įvesti kalbą:',
    'lv': 'Šīs lapas valodu nevarēja noteikt automātiski. Lūdzu norādiet pamata valodu:',
    'ml': 'ഈ വെ%u0D2C%u0D4D%u200Cസൈറ്റിന്റെ ഭാഷ കണ്ടുപിടിയ്ക്കാ%u0D28%u0D4D%u200D കഴിഞ്ഞില്ല. ഭാഷ ഏതാണെന്നു തിരഞ്ഞെടുക്കുക:',
    'nl': 'De taal van deze website kan niet automatisch worden bepaald. Geef de hoofdtaal op:',
    'no': 'Nettstedets språk kunne ikke finnes automatisk. Vennligst oppgi språk:',
    'pt': 'A língua deste site não pôde ser determinada automaticamente. Por favor indique a língua principal:',
    'ru': 'Язык этого сайта не может быть определен автоматически. Пожалуйста укажите язык:',
    'sl': 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:',
    'sv': 'Spr%E5ket p%E5 den h%E4r webbplatsen kunde inte avg%F6ras automatiskt. V%E4nligen ange:',
    'tr': 'Bu web sitesinin dili otomatik olarak tespit edilememiştir. Lütfen dökümanın dilini seçiniz%A0:',
    'uk': 'Мова цього веб-сайту не може бути визначена автоматично. Будь ласка, вкажіть головну мову:'
  },
  
  /**
   * @name Hyphenator-basePath
   * @description
   * A string storing the basepath from where Hyphenator.js was loaded.
   * This is used to load the patternfiles.
   * The basepath is determined dynamically by searching all script-tags for Hyphenator.js
   * If the path cannot be determined http://hyphenator.googlecode.com/svn/trunk/ is used as fallback.
   * @type {string}
   * @private
   * @see Hyphenator-loadPatterns
   */
  basePath = (function () {
    var s = document.getElementsByTagName('script'), i = 0, p, src, t;
    while (!!(t = s[i++])) {
      if (!t.src) {
        continue;
      }
      src = t.src;
      p = src.indexOf('Hyphenator.js');
      if (p !== -1) {
        return src.substring(0, p);
      }
    }
    return 'http://hyphenator.googlecode.com/svn/trunk/';
  }()),

  /**
   * @name Hyphenator-isLocal
   * @description
   * isLocal is true, if Hyphenator is loaded from the same domain, as the webpage, but false, if
   * it's loaded from an external source (i.e. directly from google.code)
   */
  isLocal = (function () {
    var re = false;
    if (window.location.href.indexOf(basePath) !== -1) {
      re = true;
    }
    return re;
  }()),
  
  /**
   * @name Hyphenator-documentLoaded
   * @description
   * documentLoaded is true, when the DOM has been loaded. This is set by runOnContentLoaded
   */
  documentLoaded = false,
  documentCount = 0,
  
  /**
   * @name Hyphenator-persistentConfig
   * @description
   * if persistentConfig is set to true (defaults to false), config options and the state of the 
   * toggleBox are stored in DOM-storage (according to the storage-setting). So they haven't to be
   * set for each page.
   */  
  persistentConfig = false,  

  /**
   * @name Hyphenator-contextWindow
   * @description
   * contextWindow stores the window for the document to be hyphenated.
   * If there are frames this will change.
   * So use contextWindow instead of window!
   */
  contextWindow = window,

  /**
   * @name Hyphenator-doFrames
   * @description
   * switch to control if frames/iframes should be hyphenated, too
   * defaults to false (frames are a bag of hurt!)
   */
  doFrames = false,
  
  /**
   * @name Hyphenator-dontHyphenate
   * @description
   * A key-value object containing all html-tags whose content should not be hyphenated
   * @type {Object.<string,boolean>}
   * @private
   * @see Hyphenator-hyphenateElement
   */
  dontHyphenate = {'script': true, 'code': true, 'pre': true, 'img': true, 'br': true, 'samp': true, 'kbd': true, 'var': true, 'abbr': true, 'acronym': true, 'sub': true, 'sup': true, 'button': true, 'option': true, 'label': true, 'textarea': true, 'input': true},

  /**
   * @name Hyphenator-enableCache
   * @description
   * A variable to set if caching is enabled or not
   * @type boolean
   * @default true
   * @private
   * @see Hyphenator.config
   * @see hyphenateWord
   */
  enableCache = true,

  /**
   * @name Hyphenator-storageType
   * @description
   * A variable to define what html5-DOM-Storage-Method is used ('none', 'local' or 'session')
   * @type {string}
   * @default 'none'
   * @private
   * @see Hyphenator.config
   */  
  storageType = 'local',

  /**
   * @name Hyphenator-storage
   * @description
   * An alias to the storage-Method defined in storageType.
   * Set by Hyphenator.run()
   * @type {Object|undefined}
   * @default null
   * @private
   * @see Hyphenator.run
   */  
  storage,
  
  /**
   * @name Hyphenator-enableReducedPatternSet
   * @description
   * A variable to set if storing the used patterns is set
   * @type boolean
   * @default false
   * @private
   * @see Hyphenator.config
   * @see hyphenateWord
   * @see Hyphenator.getRedPatternSet
   */  
  enableReducedPatternSet = false,
  
  /**
   * @name Hyphenator-enableRemoteLoading
   * @description
   * A variable to set if pattern files should be loaded remotely or not
   * @type boolean
   * @default true
   * @private
   * @see Hyphenator.config
   * @see Hyphenator-loadPatterns
   */
  enableRemoteLoading = true,
  
  /**
   * @name Hyphenator-displayToggleBox
   * @description
   * A variable to set if the togglebox should be displayed or not
   * @type boolean
   * @default false
   * @private
   * @see Hyphenator.config
   * @see Hyphenator-toggleBox
   */
  displayToggleBox = false,
  
  /**
   * @name Hyphenator-hyphenateClass
   * @description
   * A string containing the css-class-name for the hyphenate class
   * @type {string}
   * @default 'hyphenate'
   * @private
   * @example
   * &lt;p class = "hyphenate"&gt;Text&lt;/p&gt;
   * @see Hyphenator.config
   */
  hyphenateClass = 'hyphenate',

  /**
   * @name Hyphenator-dontHyphenateClass
   * @description
   * A string containing the css-class-name for elements that should not be hyphenated
   * @type {string}
   * @default 'donthyphenate'
   * @private
   * @example
   * &lt;p class = "donthyphenate"&gt;Text&lt;/p&gt;
   * @see Hyphenator.config
   */
  dontHyphenateClass = 'donthyphenate',
  
  /**
   * @name Hyphenator-min
   * @description
   * A number wich indicates the minimal length of words to hyphenate.
   * @type {number}
   * @default 6
   * @private
   * @see Hyphenator.config
   */  
  min = 6,
  
  /**
   * @name Hyphenator-orphanControl
   * @description
   * Control how the last words of a line are handled:
   * level 1 (default): last word is hyphenated
   * level 2: last word is not hyphenated
   * level 3: last word is not hyphenated and last space is non breaking
   * @type {number}
   * @default 1
   * @private
   */
  orphanControl = 1,
  
  /**
   * @name Hyphenator-isBookmarklet
   * @description
   * Indicates if Hyphanetor runs as bookmarklet or not.
   * @type boolean
   * @default false
   * @private
   */  
  isBookmarklet = (function () {
    var loc = null, re = false, jsArray = document.getElementsByTagName('script'), i, l;
    for (i = 0, l = jsArray.length; i < l; i++) {
      if (!!jsArray[i].getAttribute('src')) {
        loc = jsArray[i].getAttribute('src');
      }
      if (!loc) {
        continue;
      } else if (loc.indexOf('Hyphenator.js?bm=true') !== -1) {
        re = true;
      }
    }
    return re;
  }()),
  
  /**
   * @name Hyphenator-mainLanguage
   * @description
   * The general language of the document. In contrast to {@link Hyphenator-defaultLanguage},
   * mainLanguage is defined by the client (i.e. by the html or by a prompt).
   * @type {string|null}
   * @private
   * @see Hyphenator-autoSetMainLanguage
   */  
  mainLanguage = null,

  /**
   * @name Hyphenator-defaultLanguage
   * @description
   * The language defined by the developper. This language setting is defined by a config option.
   * It is overwritten by any html-lang-attribute and only taken in count, when no such attribute can
   * be found (i.e. just before the prompt).
   * @type {string|null}
   * @private
   * @see Hyphenator-autoSetMainLanguage
   */  
  defaultLanguage = '',

  /**
   * @name Hyphenator-elements
   * @description
   * An array holding all elements that have to be hyphenated. This var is filled by
   * {@link Hyphenator-gatherDocumentInfos}
   * @type {Array}
   * @private
   */  
  elements = [],
  
  /**
   * @name Hyphenator-exceptions
   * @description
   * An object containing exceptions as comma separated strings for each language.
   * When the language-objects are loaded, their exceptions are processed, copied here and then deleted.
   * @see Hyphenator-prepareLanguagesObj
   * @type {Object}
   * @private
   */  
  exceptions = {},
  
  countObjProps = function (obj) {
    var k, l = 0;
    for (k in obj) {
      if (obj.hasOwnProperty(k)) {
        l++;
      }
    }
    return l;
  },
  /**
   * @name Hyphenator-docLanguages
   * @description
   * An object holding all languages used in the document. This is filled by
   * {@link Hyphenator-gatherDocumentInfos}
   * @type {Object}
   * @private
   */  
  docLanguages = {},


  /**
   * @name Hyphenator-state
   * @description
   * A number that inidcates the current state of the script
   * 0: not initialized
   * 1: loading patterns
   * 2: ready
   * 3: hyphenation done
   * 4: hyphenation removed
   * @type {number}
   * @private
   */  
  state = 0,

  /**
   * @name Hyphenator-url
   * @description
   * A string containing a RegularExpression to match URL's
   * @type {string}
   * @private
   */  
  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#!:\\.?\\+=&%@!\\-]*)*',
  //      protocoll     usr     pwd                    ip               or                          host                 tld        port               path
  /**
   * @name Hyphenator-mail
   * @description
   * A string containing a RegularExpression to match mail-adresses
   * @type {string}
   * @private
   */  
  mail = '[\\w-\\.]+@[\\w\\.]+',

  /**
   * @name Hyphenator-urlRE
   * @description
   * A RegularExpressions-Object for url- and mail adress matching
   * @type {RegExp}
   * @private
   */    
  urlOrMailRE = new RegExp('(' + url + ')|(' + mail + ')', 'i'),

  /**
   * @name Hyphenator-zeroWidthSpace
   * @description
   * A string that holds a char.
   * Depending on the browser, this is the zero with space or an empty string.
   * zeroWidthSpace is used to break URLs
   * @type {string}
   * @private
   */    
  zeroWidthSpace = (function () {
    var zws, ua = navigator.userAgent.toLowerCase();
    zws = String.fromCharCode(8203); //Unicode zero width space
    if (ua.indexOf('msie 6') !== -1) {
      zws = ''; //IE6 doesn't support zws
    }
    if (ua.indexOf('opera') !== -1 && ua.indexOf('version/10.00') !== -1) {
      zws = ''; //opera 10 on XP doesn't support zws
    }
    return zws;
  }()),
  
  /**
   * @name Hyphenator-createElem
   * @description
   * A function alias to document.createElementNS or document.createElement
   * @type {function(string, Object)}
   * @private
   */    
  createElem = function (tagname, context) {
    context = context || contextWindow;
    if (document.createElementNS) {
      return context.document.createElementNS('http://www.w3.org/1999/xhtml', tagname);
    } else if (document.createElement) {
      return context.document.createElement(tagname);
    }
  },
  
  /**
   * @name Hyphenator-onHyphenationDone
   * @description
   * A method to be called, when the last element has been hyphenated or the hyphenation has been
   * removed from the last element.
   * @see Hyphenator.config
   * @type {function()}
   * @private
   */    
  onHyphenationDone = function () {},

  /**
   * @name Hyphenator-onError
   * @description
   * A function that can be called upon an error.
   * @see Hyphenator.config
   * @type {function(Object)}
   * @private
   */    
  onError = function (e) {
    window.alert("Hyphenator.js says:\n\nAn Error ocurred:\n" + e.message);
  },

  /**
   * @name Hyphenator-selectorFunction
   * @description
   * A function that has to return a HTMLNodeList of Elements to be hyphenated.
   * By default it uses the classname ('hyphenate') to select the elements.
   * @see Hyphenator.config
   * @type {function()}
   * @private
   */    
  selectorFunction = function () {
    var tmp, el = [], i, l;
    if (document.getElementsByClassName) {
      el = contextWindow.document.getElementsByClassName(hyphenateClass);
    } else {
      tmp = contextWindow.document.getElementsByTagName('*');
      l = tmp.length;
      for (i = 0; i < l; i++)
      {
        if (tmp[i].className.indexOf(hyphenateClass) !== -1 && tmp[i].className.indexOf(dontHyphenateClass) === -1) {
          el.push(tmp[i]);
        }
      }
    }
    return el;
  },

  /**
   * @name Hyphenator-intermediateState
   * @description
   * The value of style.visibility of the text while it is hyphenated.
   * @see Hyphenator.config
   * @type {string}
   * @private
   */    
  intermediateState = 'hidden',
  
  /**
   * @name Hyphenator-hyphen
   * @description
   * A string containing the character for in-word-hyphenation
   * @type {string}
   * @default the soft hyphen
   * @private
   * @see Hyphenator.config
   */
  hyphen = String.fromCharCode(173),
  
  /**
   * @name Hyphenator-urlhyphen
   * @description
   * A string containing the character for url/mail-hyphenation
   * @type {string}
   * @default the zero width space
   * @private
   * @see Hyphenator.config
   * @see Hyphenator-zeroWidthSpace
   */
  urlhyphen = zeroWidthSpace,

  /**
   * @name Hyphenator-safeCopy
   * @description
   * Defines wether work-around for copy issues is active or not
   * Not supported by Opera (no onCopy handler)
   * @type boolean
   * @default true
   * @private
   * @see Hyphenator.config
   * @see Hyphenator-registerOnCopy
   */
  safeCopy = true,
  
  /**
   * @name Hyphenator-Expando
   * @description
   * This custom object stores data for elements: storing data directly in elements
   * (DomElement.customData = foobar;) isn't a good idea. It would lead to conflicts
   * in form elements, when the form has a child with name="foobar". Therefore, this
   * solution follows the approach of jQuery: the data is stored in an object and
   * referenced by a unique attribute of the element. The attribute has a name that 
   * is built by the prefix "HyphenatorExpando_" and a random number, so if the very
   * very rare case occurs, that there's already an attribute with the same name, a
   * simple reload is enough to make it function.
   * @private
   */    
  Expando = (function () {
    var container = {},
      name = "HyphenatorExpando_" + Math.random(),
      uuid = 0;
    return {
      getDataForElem : function (elem) {
        return container[elem[name].id];
      },
      setDataForElem : function (elem, data) {
        var id;
        if (elem[name] && elem[name].id !== '') {
          id = elem[name].id;
        } else {
          id = uuid++;
          elem[name] = {'id': id}; //object needed, otherways it is reflected in HTML in IE
        }
        container[id] = data;
      },
      appendDataForElem : function (elem, data) {
        var k;
        for (k in data) {
          if (data.hasOwnProperty(k)) {
            container[elem[name].id][k] = data[k];
          }
        }
      },
      delDataOfElem : function (elem) {
        delete container[elem[name]];
      }
    };
  }()),
    
  /*
   * runOnContentLoaded is based od jQuery.bindReady()
   * see
   * jQuery JavaScript Library v1.3.2
   * http://jquery.com/
   *
   * Copyright (c) 2009 John Resig
   * Dual licensed under the MIT and GPL licenses.
   * http://docs.jquery.com/License
   *
   * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
   * Revision: 6246
   */
  /**
   * @name Hyphenator-runOnContentLoaded
   * @description
   * A crossbrowser solution for the DOMContentLoaded-Event based on jQuery
   * <a href = "http://jquery.com/</a>
   * I added some functionality: e.g. support for frames and iframes…
   * @param {Object} w the window-object
   * @param {function()} f the function to call onDOMContentLoaded
   * @private
   */
  runOnContentLoaded = function (w, f) {
    var DOMContentLoaded = function () {}, toplevel, hyphRunForThis = {};
    if (documentLoaded && !hyphRunForThis[w.location.href]) {
      f();
      hyphRunForThis[w.location.href] = true;
      return;
    }
    function init(context) {
      contextWindow = context || window;
      if (!hyphRunForThis[contextWindow.location.href] && (!documentLoaded || contextWindow != window.parent)) {
        documentLoaded = true;
        f();
        hyphRunForThis[contextWindow.location.href] = true;
      }
    }
    
    function doScrollCheck() {
      try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left");
      } catch (error) {
        setTimeout(doScrollCheck, 1);
        return;
      }
    
      // and execute any waiting functions
      init(window);
    }

    function doOnLoad() {
      var i, haveAccess, fl = window.frames.length;
      if (doFrames && fl > 0) {
        for (i = 0; i < fl; i++) {
          haveAccess = undefined;
          //try catch isn't enough for webkit
          try {
            //opera throws only on document.toString-access
            haveAccess = window.frames[i].document.toString();
          } catch (e) {
            haveAccess = undefined;
          }
          if (!!haveAccess) {
            init(window.frames[i]);
          }
        }
        contextWindow = window;
        f();
        hyphRunForThis[window.location.href] = true;
      } else {
        init(window);
      }
    }
    
    // Cleanup functions for the document ready method
    if (document.addEventListener) {
      DOMContentLoaded = function () {
        document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
        if (doFrames && window.frames.length > 0) {
          //we are in a frameset, so do nothing but wait for onload to fire
          return;
        } else {
          init(window);
        }
      };
    
    } else if (document.attachEvent) {
      DOMContentLoaded = function () {
        // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
        if (document.readyState === "complete") {
          document.detachEvent("onreadystatechange", DOMContentLoaded);
          if (doFrames && window.frames.length > 0) {
            //we are in a frameset, so do nothing but wait for onload to fire
            return;
          } else {
            init(window);
          }
        }
      };
    }

    // Mozilla, Opera and webkit nightlies currently support this event
    if (document.addEventListener) {
      // Use the handy event callback
      document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
      
      // A fallback to window.onload, that will always work
      window.addEventListener("load", doOnLoad, false);

    // If IE event model is used
    } else if (document.attachEvent) {
      // ensure firing before onload,
      // maybe late but safe also for iframes
      document.attachEvent("onreadystatechange", DOMContentLoaded);
      
      // A fallback to window.onload, that will always work
      window.attachEvent("onload", doOnLoad);

      // If IE and not a frame
      // continually check to see if the document is ready
      toplevel = false;
      try {
        toplevel = window.frameElement === null;
      } catch (e) {}

      if (document.documentElement.doScroll && toplevel) {
        doScrollCheck();
      }
    }

  },



  /**
   * @name Hyphenator-getLang
   * @description
   * Gets the language of an element. If no language is set, it may use the {@link Hyphenator-mainLanguage}.
   * @param {Object} el The first parameter is an DOM-Element-Object
   * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator-mainLanguage}
   * if there's no language found for the element.
   * @private
   */
  getLang = function (el, fallback) {
    if (!!el.getAttribute('lang')) {
      return el.getAttribute('lang').toLowerCase();
    }
    // The following doesn't work in IE due to a bug when getAttribute('xml:lang') in a table
    /*if (!!el.getAttribute('xml:lang')) {
      return el.getAttribute('xml:lang').substring(0, 2);
    }*/
    //instead, we have to do this (thanks to borgzor):
    try {
      if (!!el.getAttribute('xml:lang')) {
        return el.getAttribute('xml:lang').toLowerCase();
      }
    } catch (ex) {}
    if (el.tagName !== 'HTML') {
      return getLang(el.parentNode, true);
    }
    if (fallback) {
      return mainLanguage;
    }
    return null;
  },
  
  /**
   * @name Hyphenator-autoSetMainLanguage
   * @description
   * Retrieves the language of the document from the DOM.
   * The function looks in the following places:
   * <ul>
   * <li>lang-attribute in the html-tag</li>
   * <li>&lt;meta http-equiv = "content-language" content = "xy" /&gt;</li>
   * <li>&lt;meta name = "DC.Language" content = "xy" /&gt;</li>
   * <li>&lt;meta name = "language" content = "xy" /&gt;</li>
   * </li>
   * If nothing can be found a prompt using {@link Hyphenator-languageHint} and {@link Hyphenator-prompterStrings} is displayed.
   * If the retrieved language is in the object {@link Hyphenator-supportedLang} it is copied to {@link Hyphenator-mainLanguage}
   * @private
   */    
  autoSetMainLanguage = function (w) {
    w = w || contextWindow;
    var el = w.document.getElementsByTagName('html')[0],
      m = w.document.getElementsByTagName('meta'),
      i, text, e, ul;
    mainLanguage = getLang(el, false);
    if (!mainLanguage) {
      for (i = 0; i < m.length; i++) {
        //<meta http-equiv = "content-language" content="xy">  
        if (!!m[i].getAttribute('http-equiv') && (m[i].getAttribute('http-equiv').toLowerCase() === 'content-language')) {
          mainLanguage = m[i].getAttribute('content').toLowerCase();
        }
        //<meta name = "DC.Language" content="xy">
        if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'dc.language')) {
          mainLanguage = m[i].getAttribute('content').toLowerCase();
        }      
        //<meta name = "language" content = "xy">
        if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'language')) {
          mainLanguage = m[i].getAttribute('content').toLowerCase();
        }
      }
    }
    //get lang for frame from enclosing document
    if (!mainLanguage && doFrames && contextWindow != window.parent) {
      autoSetMainLanguage(window.parent);
    }
    //fallback to defaultLang if set
    if (!mainLanguage && defaultLanguage !== '') {
      mainLanguage = defaultLanguage;
    }
    //ask user for lang
    if (!mainLanguage) {
      text = '';
      ul = navigator.language ? navigator.language : navigator.userLanguage;
      ul = ul.substring(0, 2);
      if (prompterStrings.hasOwnProperty(ul)) {
        text = prompterStrings[ul];
      } else {
        text = prompterStrings.en;
      }
      text += ' (ISO 639-1)\n\n' + languageHint;
      mainLanguage = window.prompt(unescape(text), ul).toLowerCase();
    }
    if (!supportedLang.hasOwnProperty(mainLanguage)) {
      if (supportedLang.hasOwnProperty(mainLanguage.split('-')[0])) { //try subtag
        mainLanguage = mainLanguage.split('-')[0];
      } else {
        e = new Error('The language "' + mainLanguage + '" is not yet supported.');
        throw e;
      }
    }
  },
    
  /**
   * @name Hyphenator-gatherDocumentInfos
   * @description
   * This method runs through the DOM and executes the process()-function on:
   * - every node returned by the {@link Hyphenator-selectorFunction}.
   * The process()-function copies the element to the elements-variable, sets its visibility
   * to intermediateState, retrieves its language and recursivly descends the DOM-tree until
   * the child-Nodes aren't of type 1
   * @private
   */    
  gatherDocumentInfos = function () {
    var elToProcess, tmp, i = 0,
    process = function (el, hide, lang) {
      var n, i = 0, hyphenatorSettings = {};
      if (hide && intermediateState === 'hidden') {
        if (!!el.getAttribute('style')) {
          hyphenatorSettings.hasOwnStyle = true;
        } else {
          hyphenatorSettings.hasOwnStyle = false;          
        }
        hyphenatorSettings.isHidden = true;
        el.style.visibility = 'hidden';
      }
      if (el.lang && typeof(el.lang) === 'string') {
        hyphenatorSettings.language = el.lang.toLowerCase(); //copy attribute-lang to internal lang
      } else if (lang) {
        hyphenatorSettings.language = lang.toLowerCase();
      } else {
        hyphenatorSettings.language = getLang(el, true);
      }
      lang = hyphenatorSettings.language;
      if (supportedLang[lang]) {
        docLanguages[lang] = true;
      } else {
        if (supportedLang.hasOwnProperty(lang.split('-')[0])) { //try subtag
          lang = lang.split('-')[0];
          hyphenatorSettings.language = lang;
        } else if (!isBookmarklet) {
          onError(new Error('Language ' + lang + ' is not yet supported.'));
        }
      }
      Expando.setDataForElem(el, hyphenatorSettings);      
      
      elements.push(el);
      while (!!(n = el.childNodes[i++])) {
        if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] &&
          n.className.indexOf(dontHyphenateClass) === -1 && !(n in elToProcess)) {
          process(n, false, lang);
        }
      }
    };
    if (isBookmarklet) {
      elToProcess = contextWindow.document.getElementsByTagName('body')[0];
      process(elToProcess, false, mainLanguage);
    } else {
      elToProcess = selectorFunction();
      while (!!(tmp = elToProcess[i++]))
      {
        process(tmp, true, '');
      }      
    }
    if (!Hyphenator.languages.hasOwnProperty(mainLanguage)) {
      docLanguages[mainLanguage] = true;
    } else if (!Hyphenator.languages[mainLanguage].prepared) {
      docLanguages[mainLanguage] = true;
    }
    if (elements.length > 0) {
      Expando.appendDataForElem(elements[elements.length - 1], {isLast : true});
    }
  },
     
  /**
   * @name Hyphenator-convertPatterns
   * @description
   * Converts the patterns from string '_a6' to object '_a':'_a6'.
   * The result is stored in the {@link Hyphenator-patterns}-object.
   * @private
   * @param {string} lang the language whose patterns shall be converted
   */    
  convertPatterns = function (lang) {
    var plen, anfang, ende, pats, pat, key, tmp = {};
    pats = Hyphenator.languages[lang].patterns;
    for (plen in pats) {
      if (pats.hasOwnProperty(plen)) {
        plen = parseInt(plen, 10);
        anfang = 0;
        ende = plen;
        while (!!(pat = pats[plen].substring(anfang, ende))) {
          key = pat.replace(/\d/g, '');
          tmp[key] = pat;
          anfang = ende;
          ende += plen;
        }
      }
    }
    Hyphenator.languages[lang].patterns = tmp;
    Hyphenator.languages[lang].patternsConverted = true;
  },

  /**
   * @name Hyphenator-convertExceptionsToObject
   * @description
   * Converts a list of comma seprated exceptions to an object:
   * 'Fortran,Hy-phen-a-tion' -> {'Fortran':'Fortran','Hyphenation':'Hy-phen-a-tion'}
   * @private
   * @param {string} exc a comma separated string of exceptions (without spaces)
   */    
  convertExceptionsToObject = function (exc) {
    var w = exc.split(', '),
      r = {},
      i, l, key;
    for (i = 0, l = w.length; i < l; i++) {
      key = w[i].replace(/-/g, '');
      if (!r.hasOwnProperty(key)) {
        r[key] = w[i];
      }
    }
    return r;
  },
  
  /**
   * @name Hyphenator-loadPatterns
   * @description
   * Adds a &lt;script&gt;-Tag to the DOM to load an externeal .js-file containing patterns and settings for the given language.
   * If the given language is not in the {@link Hyphenator-supportedLang}-Object it returns.
   * One may ask why we are not using AJAX to load the patterns. The XMLHttpRequest-Object 
   * has a same-origin-policy. This makes the isBookmarklet-functionality impossible.
   * @param {string} lang The language to load the patterns for
   * @private
   * @see Hyphenator-basePath
   */
  loadPatterns = function (lang) {
    var url, xhr, head, script;
    if (supportedLang[lang] && !Hyphenator.languages[lang]) {
          url = basePath + supportedLang[lang];
    } else {
      return;
    }
    if (isLocal && !isBookmarklet) {
      //check if 'url' is available:
      xhr = null;
      if (typeof XMLHttpRequest !== 'undefined') {
        xhr = new XMLHttpRequest();
      }
      if (!xhr) {
        try {
          xhr  = new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
          xhr  = null;
        }
      }
      if (xhr) {
        xhr.open('HEAD', url, false);
        xhr.setRequestHeader('Cache-Control', 'no-cache');
        xhr.send(null);
        if (xhr.status === 404) {
          onError(new Error('Could not load\n' + url));
          delete docLanguages[lang];
          return;
        }
      }
    }
    if (createElem) {
      head = window.document.getElementsByTagName('head').item(0);
      script = createElem('script', window);
      script.src = url;
      script.type = 'text/javascript';
      head.appendChild(script);
    }
  },
  
  /**
   * @name Hyphenator-prepareLanguagesObj
   * @description
   * Adds a cache to each language and converts the exceptions-list to an object.
   * If storage is active the object is stored there.
   * @private
   * @param {string} lang the language ob the lang-obj
   */    
  prepareLanguagesObj = function (lang) {
    var lo = Hyphenator.languages[lang], wrd;
    if (!lo.prepared) {  
      if (enableCache) {
        lo.cache = {};
        //Export
        lo['cache'] = lo.cache;
      }
      if (enableReducedPatternSet) {
        lo.redPatSet = {};
      }
      //add exceptions from the pattern file to the local 'exceptions'-obj
      if (lo.hasOwnProperty('exceptions')) {
        Hyphenator.addExceptions(lang, lo.exceptions);
        delete lo.exceptions;
      }
      //copy global exceptions to the language specific exceptions
      if (exceptions.hasOwnProperty('global')) {
        if (exceptions.hasOwnProperty(lang)) {
          exceptions[lang] += ', ' + exceptions.global;
        } else {
          exceptions[lang] = exceptions.global;
        }
      }
      //move exceptions from the the local 'exceptions'-obj to the 'language'-object
      if (exceptions.hasOwnProperty(lang)) {
        lo.exceptions = convertExceptionsToObject(exceptions[lang]);
        delete exceptions[lang];
      } else {
        lo.exceptions = {};
      }
      convertPatterns(lang);
      wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
      lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi');
      lo.prepared = true;
    }
    if (!!storage) {
      try {
        storage.setItem('Hyphenator_' + lang, window.JSON.stringify(lo));
      } catch (e) {
        //onError(e);
      }
    }
    
  },
  
  /**
   * @name Hyphenator-prepare
   * @description
   * This funtion prepares the Hyphenator-Object: If RemoteLoading is turned off, it assumes
   * that the patternfiles are loaded, all conversions are made and the callback is called.
   * If storage is active the object is retrieved there.
   * If RemoteLoading is on (default), it loads the pattern files and waits until they are loaded,
   * by repeatedly checking Hyphenator.languages. If a patterfile is loaded the patterns are
   * converted to their object style and the lang-object extended.
   * Finally the callback is called.
   * @param {function()} callback to call, when all patterns are loaded
   * @private
   */
  prepare = function (callback) {
    var lang, interval, tmp1, tmp2;
    if (!enableRemoteLoading) {
      for (lang in Hyphenator.languages) {
        if (Hyphenator.languages.hasOwnProperty(lang)) {
          prepareLanguagesObj(lang);
        }
      }
      state = 2;
      callback();
      return;
    }
    // get all languages that are used and preload the patterns
    state = 1;
    for (lang in docLanguages) {
      if (docLanguages.hasOwnProperty(lang)) {
        if (!!storage && storage.getItem('Hyphenator_' + lang)) {
          Hyphenator.languages[lang] = window.JSON.parse(storage.getItem('Hyphenator_' + lang));
          if (exceptions.hasOwnProperty('global')) {
            tmp1 = convertExceptionsToObject(exceptions.global);
            for (tmp2 in tmp1) {
              if (tmp1.hasOwnProperty(tmp2)) {
                Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
              }
            }
          }
          //Replace exceptions since they may have been changed:
          if (exceptions.hasOwnProperty(lang)) {
            tmp1 = convertExceptionsToObject(exceptions[lang]);
            for (tmp2 in tmp1) {
              if (tmp1.hasOwnProperty(tmp2)) {
                Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
              }
            }
            delete exceptions[lang];
          }
          //Replace genRegExp since it may have been changed:
          tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
          Hyphenator.languages[lang].genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + tmp1 + ')', 'gi');
          
          delete docLanguages[lang];
          continue;
        } else {
          loadPatterns(lang);
        }
      }
    }
    // if all patterns are loaded from storage: callback
    if (countObjProps(docLanguages) === 0) {
      state = 2;
      callback();
      return;
    }
    // else async wait until patterns are loaded, then callback
    interval = window.setInterval(function () {
      var finishedLoading = true, lang;
      for (lang in docLanguages) {
        if (docLanguages.hasOwnProperty(lang)) {
          finishedLoading = false;
          if (!!Hyphenator.languages[lang]) {
            delete docLanguages[lang];
            //do conversion while other patterns are loading:
            prepareLanguagesObj(lang);
          }
        }
      }
      if (finishedLoading) {
        //console.log('callig callback for ' + contextWindow.location.href);
        window.clearInterval(interval);
        state = 2;
        callback();
      }
    }, 100);
  },

  /**
   * @name Hyphenator-switchToggleBox
   * @description
   * Creates or hides the toggleBox: a small button to turn off/on hyphenation on a page.
   * @see Hyphenator.config
   * @private
   */    
  toggleBox = function () {
    var myBox, bdy, myIdAttribute, myTextNode, myClassAttribute,
    text = (Hyphenator.doHyphenation ? 'Hy-phen-a-tion' : 'Hyphenation');
    if (!!(myBox = contextWindow.document.getElementById('HyphenatorToggleBox'))) {
      myBox.firstChild.data = text;
    } else {
      bdy = contextWindow.document.getElementsByTagName('body')[0];
      myBox = createElem('div', contextWindow);
      myIdAttribute = contextWindow.document.createAttribute('id');
      myIdAttribute.nodeValue = 'HyphenatorToggleBox';
      myClassAttribute = contextWindow.document.createAttribute('class');
      myClassAttribute.nodeValue = dontHyphenateClass;
      myTextNode = contextWindow.document.createTextNode(text);
      myBox.appendChild(myTextNode);
      myBox.setAttributeNode(myIdAttribute);
      myBox.setAttributeNode(myClassAttribute);
      myBox.onclick =  Hyphenator.toggleHyphenation;
      myBox.style.position = 'absolute';
      myBox.style.top = '0px';
      myBox.style.right = '0px';
      myBox.style.margin = '0';
      myBox.style.backgroundColor = '#AAAAAA';
      myBox.style.color = '#FFFFFF';
      myBox.style.font = '6pt Arial';
      myBox.style.letterSpacing = '0.2em';
      myBox.style.padding = '3px';
      myBox.style.cursor = 'pointer';
      myBox.style.WebkitBorderBottomLeftRadius = '4px';
      myBox.style.MozBorderRadiusBottomleft = '4px';
      bdy.appendChild(myBox);
    }
  },

  /**
   * @name Hyphenator-hyphenateWord
   * @description
   * This function is the heart of Hyphenator.js. It returns a hyphenated word.
   *
   * If there's already a {@link Hyphenator-hypen} in the word, the word is returned as it is.
   * If the word is in the exceptions list or in the cache, it is retrieved from it.
   * If there's a '-' put a zeroWidthSpace after the '-' and hyphenate the parts.
   * @param {string} lang The language of the word
   * @param {string} word The word
   * @returns string The hyphenated word
   * @public
   */  
  hyphenateWord = function (lang, word) {
    var lo = Hyphenator.languages[lang],
      parts, i, l, w, wl, s, hypos, p, maxwins, win, pat = false, patk, c, t, n, numb3rs, inserted, hyphenatedword, val, subst, ZWNJpos = [];
    if (word === '') {
      return '';
    }
    if (word.indexOf(hyphen) !== -1) {
      //word already contains shy; -> leave at it is!
      return word;
    }
    if (enableCache && lo.cache.hasOwnProperty(word)) { //the word is in the cache
      return lo.cache[word];
    }
    if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list
      return lo.exceptions[word].replace(/-/g, hyphen);
    }
    if (word.indexOf('-') !== -1) {
      //word contains '-' -> hyphenate the parts separated with '-'
      parts = word.split('-');
      for (i = 0, l = parts.length; i < l; i++) {
        parts[i] = hyphenateWord(lang, parts[i]);
      }
      return parts.join('-');
    }
    w = '_' + word + '_';
    if (word.indexOf(String.fromCharCode(8204)) !== -1) {
      parts = w.split(String.fromCharCode(8204));
      w = parts.join('');
      for (i = 0, l = parts.length; i < l; i++) {
        parts[i] = parts[i].length.toString();
      }
      parts.pop();
      ZWNJpos = parts;
    }
    wl = w.length;
    s = w.split('');
    if (!!lo.charSubstitution) {
      for (subst in lo.charSubstitution) {
        if (lo.charSubstitution.hasOwnProperty(subst)) {
          w = w.replace(new RegExp(subst, 'g'), lo.charSubstitution[subst]);
        }
      }
    }
    if (word.indexOf("'") !== -1) {
      w = w.toLowerCase().replace("'", "’"); //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns)
    } else {
      w = w.toLowerCase();
    }
    //finally the core hyphenation algorithm
    hypos = [];
    numb3rs = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}; //check for member is faster then isFinite()
    n = wl - lo.shortestPattern;
    for (p = 0; p <= n; p++) {
      maxwins = Math.min((wl - p), lo.longestPattern);
      for (win = lo.shortestPattern; win <= maxwins; win++) {
        if (lo.patterns.hasOwnProperty(patk = w.substring(p, p + win))) {
          pat = lo.patterns[patk];
          if (enableReducedPatternSet && (typeof pat === 'string')) {
            lo.redPatSet[patk] = pat;
          }
          if (typeof pat === 'string') {
            //convert from string 'a5b' to array [1,5] (pos,value)
            t = 0;
            val = [];
            for (i = 0; i < pat.length; i++) {
              if (!!(c = numb3rs[pat.charAt(i)])) {
                val.push(i - t, c);
                t++;                
              }
            }
            pat = lo.patterns[patk] = val;
          }
        } else {
          continue;
        }
        for (i = 0; i < pat.length; i++) {
          c = p - 1 + pat[i];
          if (!hypos[c] || hypos[c] < pat[i + 1]) {
            hypos[c] = pat[i + 1];
          }
          i++;
        }
      }
    }
    inserted = 0;
    for (i = lo.leftmin; i <= (wl - 2 - lo.rightmin); i++) {
      if (ZWNJpos.length > 0 && ZWNJpos[0] === i) {
        ZWNJpos.shift();
        s.splice(i + inserted - 1, 0, String.fromCharCode(8204));
        inserted++;
      }      
      if (!!(hypos[i] & 1)) {
        s.splice(i + inserted + 1, 0, hyphen);
        inserted++;
      }
    }
    hyphenatedword = s.slice(1, -1).join('');
    if (enableCache) {
      lo.cache[word] = hyphenatedword;
    }
    return hyphenatedword;
  },
    
  /**
   * @name Hyphenator-hyphenateURL
   * @description
   * Puts {@link Hyphenator-urlhyphen} after each no-alphanumeric char that my be in a URL.
   * @param {string} url to hyphenate
   * @returns string the hyphenated URL
   * @public
   */
  hyphenateURL = function (url) {
    return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen);
  },

  /**
   * @name Hyphenator-removeHyphenationFromElement
   * @description
   * Removes all hyphens from the element. If there are other elements, the function is
   * called recursively.
   * Removing hyphens is usefull if you like to copy text. Some browsers are buggy when the copy hyphenated texts.
   * @param {Object} el The element where to remove hyphenation.
   * @public
   */
  removeHyphenationFromElement = function (el) {
    var h, i = 0, n;
    switch (hyphen) {
    case '|':
      h = '\\|';
      break;
    case '+':
      h = '\\+';
      break;
    case '*':
      h = '\\*';
      break;
    default:
      h = hyphen;
    }
    while (!!(n = el.childNodes[i++])) {
      if (n.nodeType === 3) {
        n.data = n.data.replace(new RegExp(h, 'g'), '');
        n.data = n.data.replace(new RegExp(zeroWidthSpace, 'g'), '');
      } else if (n.nodeType === 1) {
        removeHyphenationFromElement(n);
      }
    }
  },
  
  
  /**
   * @name Hyphenator-registerOnCopy
   * @description
   * Huge work-around for browser-inconsistency when it comes to
   * copying of hyphenated text.
   * The idea behind this code has been provided by http://github.com/aristus/sweet-justice
   * sweet-justice is under BSD-License
   * @private
   */
  registerOnCopy = function (el) {
    var body = el.ownerDocument.getElementsByTagName('body')[0],
    shadow,
    selection,
    range,
    rangeShadow,
    restore,
    oncopyHandler = function (e) {
      e = e || window.event;
      var target = e.target || e.srcElement,
      currDoc = target.ownerDocument,
      body = currDoc.getElementsByTagName('body')[0],
      targetWindow = 'defaultView' in currDoc ? currDoc.defaultView : currDoc.parentWindow;
      if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) {
        //Safari needs this
        return;
      }
      //create a hidden shadow element
      shadow = currDoc.createElement('div');
      shadow.style.overflow = 'hidden';
      shadow.style.position = 'absolute';
      shadow.style.top = '-5000px';
      shadow.style.height = '1px';
      body.appendChild(shadow);
      if (!!window.getSelection) {
        //FF3, Webkit
        selection = targetWindow.getSelection();
        range = selection.getRangeAt(0);
        shadow.appendChild(range.cloneContents());
        removeHyphenationFromElement(shadow);
        selection.selectAllChildren(shadow);
        restore = function () {
          shadow.parentNode.removeChild(shadow);
          selection.addRange(range);
        };
      } else {
        // IE
        selection = targetWindow.document.selection;
        range = selection.createRange();
        shadow.innerHTML = range.htmlText;
        removeHyphenationFromElement(shadow);
        rangeShadow = body.createTextRange();
        rangeShadow.moveToElementText(shadow);
        rangeShadow.select();
        restore = function () {
          shadow.parentNode.removeChild(shadow);
          if (range.text !== "") {
            range.select();
          }
        };
      }
      window.setTimeout(restore, 0);
    };
    if (!body) {
      return;
    }
    el = el || body;
    if (window.addEventListener) {
      el.addEventListener("copy", oncopyHandler, false);
    } else {
      el.attachEvent("oncopy", oncopyHandler);
    }
  },
  

  /**
   * @name Hyphenator-hyphenateElement
   * @description
   * Takes the content of the given element and - if there's text - replaces the words
   * by hyphenated words. If there's another element, the function is called recursively.
   * When all words are hyphenated, the visibility of the element is set to 'visible'.
   * @param {Object} el The element to hyphenate
   * @private
   */
  hyphenateElement = function (el) {
    var hyphenatorSettings = Expando.getDataForElem(el),
      lang = hyphenatorSettings.language, hyphenate, n, i,
      controlOrphans = function (part) {
        var h, r;
        switch (hyphen) {
        case '|':
          h = '\\|';
          break;
        case '+':
          h = '\\+';
          break;
        case '*':
          h = '\\*';
          break;
        default:
          h = hyphen;
        }
        if (orphanControl >= 2) {
          //remove hyphen points from last word
          r = part.split(' ');
          r[1] = r[1].replace(new RegExp(h, 'g'), '');
          r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), '');
          r = r.join(' ');
        }
        if (orphanControl === 3) {
          //replace spaces by non breaking spaces
          r = r.replace(/[ ]+/g, String.fromCharCode(160));
        }
        return r;
      };
    if (Hyphenator.languages.hasOwnProperty(lang)) {
      hyphenate = function (word) {
        if (!Hyphenator.doHyphenation) {
          return word;
        } else if (urlOrMailRE.test(word)) {
          return hyphenateURL(word);
        } else {
          return hyphenateWord(lang, word);
        }
      };
      if (safeCopy && (el.tagName.toLowerCase() !== 'body')) {
        registerOnCopy(el);
      }
      i = 0;
      while (!!(n = el.childNodes[i++])) {
        if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate!
          n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
          if (orphanControl !== 1) {
            n.data = n.data.replace(/[\S]+ [\S]+$/, controlOrphans);
          }
        }
      }
    }
    if (hyphenatorSettings.isHidden && intermediateState === 'hidden') {
      el.style.visibility = 'visible';
      if (!hyphenatorSettings.hasOwnStyle) {
        el.setAttribute('style', ''); // without this, removeAttribute doesn't work in Safari (thanks to molily)
        el.removeAttribute('style');
      } else {
        if (el.style.removeProperty) {
          el.style.removeProperty('visibility');
        } else if (el.style.removeAttribute) { // IE
          el.style.removeAttribute('visibility');
        }  
      }
    }
    if (hyphenatorSettings.isLast) {
      state = 3;
      documentCount--;
      if (documentCount > (-1000) && documentCount <= 0) {
        documentCount = (-2000);
        onHyphenationDone();
      }
    }
  },
  

  /**
   * @name Hyphenator-hyphenateDocument
   * @description
   * Calls hyphenateElement() for all members of elements. This is done with a setTimout
   * to prevent a "long running Script"-alert when hyphenating large pages.
   * Therefore a tricky bind()-function was necessary.
   * @private
   */
  hyphenateDocument = function () {
    function bind(fun, arg) {
      return function () {
        return fun(arg);
      };
    }
    var i = 0, el;
    while (!!(el = elements[i++])) {
      if (el.ownerDocument.location.href === contextWindow.location.href) {
        window.setTimeout(bind(hyphenateElement, el), 0);
      }
    }
  },

  /**
   * @name Hyphenator-removeHyphenationFromDocument
   * @description
   * Does what it says ;-)
   * @private
   */
  removeHyphenationFromDocument = function () {
    var i = 0, el;
    while (!!(el = elements[i++])) {
      removeHyphenationFromElement(el);
    }
    state = 4;
  },
    
  /**
   * @name Hyphenator-createStorage
   * @description
   * inits the private var storage depending of the setting in storageType
   * and the supported features of the system.
   * @private
   */
  createStorage = function () {
    try {
      if (storageType !== 'none' &&
        typeof(window.localStorage) !== 'undefined' &&
        typeof(window.sessionStorage) !== 'undefined' &&
        typeof(window.JSON.stringify) !== 'undefined' &&
        typeof(window.JSON.parse) !== 'undefined') {
        switch (storageType) {
        case 'session':
          storage = window.sessionStorage;
          break;
        case 'local':
          storage = window.localStorage;
          break;
        default:
          storage = undefined;
          break;
        }
      }
    } catch (f) {
      //FF throws an error if DOM.storage.enabled is set to false
    }
  },
  
  /**
   * @name Hyphenator-storeConfiguration
   * @description
   * Stores the current config-options in DOM-Storage
   * @private
   */
  storeConfiguration = function () {
    if (!storage) {
      return;
    }
    var settings = {
      'STORED': true,
      'classname': hyphenateClass,
      'donthyphenateclassname': dontHyphenateClass,
      'minwordlength': min,
      'hyphenchar': hyphen,
      'urlhyphenchar': urlhyphen,
      'togglebox': toggleBox,
      'displaytogglebox': displayToggleBox,
      'remoteloading': enableRemoteLoading,
      'enablecache': enableCache,
      'onhyphenationdonecallback': onHyphenationDone,
      'onerrorhandler': onError,
      'intermediatestate': intermediateState,
      'selectorfunction': selectorFunction,
      'safecopy': safeCopy,
      'doframes': doFrames,
      'storagetype': storageType,
      'orphancontrol': orphanControl,
      'dohyphenation': Hyphenator.doHyphenation,
      'persistentconfig': persistentConfig,
      'defaultlanguage': defaultLanguage
    };
    storage.setItem('Hyphenator_config', window.JSON.stringify(settings));
  },
  
  /**
   * @name Hyphenator-restoreConfiguration
   * @description
   * Retrieves config-options from DOM-Storage and does configuration accordingly
   * @private
   */
  restoreConfiguration = function () {
    var settings;
    if (storage.getItem('Hyphenator_config')) {
      settings = window.JSON.parse(storage.getItem('Hyphenator_config'));
      Hyphenator.config(settings);
    }
  };

  return {
    
    /**
     * @name Hyphenator.version
     * @memberOf Hyphenator
     * @description
     * String containing the actual version of Hyphenator.js
     * [major release].[minor releas].[bugfix release]
     * major release: new API, new Features, big changes
     * minor release: new languages, improvements
     * @public
         */    
    version: '3.3.0',

    /**
     * @name Hyphenator.doHyphenation
     * @description
     * If doHyphenation is set to false (defaults to true), hyphenateDocument() isn't called.
     * All other actions are performed.
     */    
    doHyphenation: true,
    
    /**
     * @name Hyphenator.languages
     * @memberOf Hyphenator
     * @description
     * Objects that holds key-value pairs, where key is the language and the value is the
     * language-object loaded from (and set by) the pattern file.
     * The language object holds the following members:
     * <table>
     * <tr><th>key</th><th>desc></th></tr>
     * <tr><td>leftmin</td><td>The minimum of chars to remain on the old line</td></tr>
     * <tr><td>rightmin</td><td>The minimum of chars to go on the new line</td></tr>
     * <tr><td>shortestPattern</td><td>The shortes pattern (numbers don't count!)</td></tr>
     * <tr><td>longestPattern</td><td>The longest pattern (numbers don't count!)</td></tr>
     * <tr><td>specialChars</td><td>Non-ASCII chars in the alphabet.</td></tr>
     * <tr><td>patterns</td><td>the patterns</td></tr>
     * </table>
     * And optionally (or after prepareLanguagesObj() has been called):
     * <table>
     * <tr><td>exceptions</td><td>Excpetions for the secified language</td></tr>
     * </table>
     * @public
         */    
    languages: {},
    

    /**
     * @name Hyphenator.config
       * @description
     * Config function that takes an object as an argument. The object contains key-value-pairs
     * containig Hyphenator-settings. This is a shortcut for calling Hyphenator.set...-Methods.
     * @param {Object} obj <table>
     * <tr><th>key</th><th>values</th><th>default</th></tr>
     * <tr><td>classname</td><td>string</td><td>'hyphenate'</td></tr>
     * <tr><td>donthyphenateclassname</td><td>string</td><td>''</td></tr>
     * <tr><td>minwordlength</td><td>integer</td><td>6</td></tr>
     * <tr><td>hyphenchar</td><td>string</td><td>'&amp;shy;'</td></tr>
     * <tr><td>urlhyphenchar</td><td>string</td><td>'zero with space'</td></tr>
     * <tr><td>togglebox</td><td>function</td><td>see code</td></tr>
     * <tr><td>displaytogglebox</td><td>boolean</td><td>false</td></tr>
     * <tr><td>remoteloading</td><td>boolean</td><td>true</td></tr>
     * <tr><td>enablecache</td><td>boolean</td><td>true</td></tr>
     * <tr><td>enablereducedpatternset</td><td>boolean</td><td>false</td></tr>
     * <tr><td>onhyphenationdonecallback</td><td>function</td><td>empty function</td></tr>
     * <tr><td>onerrorhandler</td><td>function</td><td>alert(onError)</td></tr>
     * <tr><td>intermediatestate</td><td>string</td><td>'hidden'</td></tr>
     * <tr><td>selectorfunction</td><td>function</td><td>[…]</td></tr>
     * <tr><td>safecopy</td><td>boolean</td><td>true</td></tr>
     * <tr><td>doframes</td><td>boolean</td><td>false</td></tr>
     * <tr><td>storagetype</td><td>string</td><td>'none'</td></tr>
     * </table>
     * @public
     * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *     Hyphenator.config({'minwordlength':4,'hyphenchar':'|'});
         *     Hyphenator.run();
         * &lt;/script&gt;
         */
    config: function (obj) {
      var assert = function (name, type) {
          if (typeof obj[name] === type) {
            return true;
          } else {
            onError(new Error('Config onError: ' + name + ' must be of type ' + type));
            return false;
          }
        },
        key;

      if (obj.hasOwnProperty('storagetype')) {
        if (assert('storagetype', 'string')) {
          storageType = obj.storagetype;
        }
        if (!storage) {
          createStorage();
        }      
      }
      if (!obj.hasOwnProperty('STORED') && storage && obj.hasOwnProperty('persistentconfig') && obj.persistentconfig === true) {
        restoreConfiguration();
      }
      
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          switch (key) {
          case 'STORED':
            break;
          case 'classname':
            if (assert('classname', 'string')) {
              hyphenateClass = obj[key];
            }
            break;
          case 'donthyphenateclassname':
            if (assert('donthyphenateclassname', 'string')) {
              dontHyphenateClass = obj[key];
            }            
            break;
          case 'minwordlength':
            if (assert('minwordlength', 'number')) {
              min = obj[key];
            }
            break;
          case 'hyphenchar':
            if (assert('hyphenchar', 'string')) {
              if (obj.hyphenchar === '&shy;') {
                obj.hyphenchar = String.fromCharCode(173);
              }
              hyphen = obj[key];
            }
            break;
          case 'urlhyphenchar':
            if (obj.hasOwnProperty('urlhyphenchar')) {
              if (assert('urlhyphenchar', 'string')) {
                urlhyphen = obj[key];
              }
            }
            break;
          case 'togglebox':
            if (assert('togglebox', 'function')) {
              toggleBox = obj[key];
            }
            break;
          case 'displaytogglebox':
            if (assert('displaytogglebox', 'boolean')) {
              displayToggleBox = obj[key];
            }
            break;
          case 'remoteloading':
            if (assert('remoteloading', 'boolean')) {
              enableRemoteLoading = obj[key];
            }
            break;
          case 'enablecache':
            if (assert('enablecache', 'boolean')) {
              enableCache = obj[key];
            }
            break;
          case 'enablereducedpatternset':
            if (assert('enablereducedpatternset', 'boolean')) {
              enableReducedPatternSet = obj[key];
            }
            break;
          case 'onhyphenationdonecallback':
            if (assert('onhyphenationdonecallback', 'function')) {
              onHyphenationDone = obj[key];
            }
            break;
          case 'onerrorhandler':
            if (assert('onerrorhandler', 'function')) {
              onError = obj[key];
            }
            break;
          case 'intermediatestate':
            if (assert('intermediatestate', 'string')) {
              intermediateState = obj[key];
            }
            break;
          case 'selectorfunction':
            if (assert('selectorfunction', 'function')) {
              selectorFunction = obj[key];
            }
            break;
          case 'safecopy':
            if (assert('safecopy', 'boolean')) {
              safeCopy = obj[key];
            }
            break;
          case 'doframes':
            if (assert('doframes', 'boolean')) {
              doFrames = obj[key];
            }
            break;
          case 'storagetype':
            if (assert('storagetype', 'string')) {
              storageType = obj[key];
            }            
            break;
          case 'orphancontrol':
            if (assert('orphancontrol', 'number')) {
              orphanControl = obj[key];
            }
            break;
          case 'dohyphenation':
            if (assert('dohyphenation', 'boolean')) {
              Hyphenator.doHyphenation = obj[key];
            }
            break;
          case 'persistentconfig':
            if (assert('persistentconfig', 'boolean')) {
              persistentConfig = obj[key];
            }
            break;
          case 'defaultlanguage':
            if (assert('defaultlanguage', 'string')) {
              defaultLanguage = obj[key];
            }
            break;
          default:
            onError(new Error('Hyphenator.config: property ' + key + ' not known.'));
          }
        }
      }
      if (storage && persistentConfig) {
        storeConfiguration();
      }
    },

    /**
     * @name Hyphenator.run
       * @description
     * Bootstrap function that starts all hyphenation processes when called.
     * @public
     * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *   Hyphenator.run();
         * &lt;/script&gt;
         */
    run: function () {
      documentCount = 0;
      var process = function () {
        try {
          if (contextWindow.document.getElementsByTagName('frameset').length > 0) {
            return; //we are in a frameset
          }
          documentCount++;
          autoSetMainLanguage(undefined);
          gatherDocumentInfos();
          //console.log('preparing for ' + contextWindow.location.href);
          prepare(hyphenateDocument);
          if (displayToggleBox) {
            toggleBox();
          }
        } catch (e) {
          onError(e);
        }
      }, i, haveAccess, fl = window.frames.length;
      
      if (!storage) {
        createStorage();
      }
      if (!documentLoaded && !isBookmarklet) {
        runOnContentLoaded(window, process);
      }
      if (isBookmarklet || documentLoaded) {
        if (doFrames && fl > 0) {
          for (i = 0; i < fl; i++) {
            haveAccess = undefined;
            //try catch isn't enough for webkit
            try {
              //opera throws only on document.toString-access
              haveAccess = window.frames[i].document.toString();
            } catch (e) {
              haveAccess = undefined;
            }
            if (!!haveAccess) {
              contextWindow = window.frames[i];
              process();
            }            
          }
        }
        contextWindow = window;
        process();
      }
    },
    
    /**
     * @name Hyphenator.addExceptions
       * @description
     * Adds the exceptions from the string to the appropriate language in the 
     * {@link Hyphenator-languages}-object
     * @param {string} lang The language
     * @param {string} words A comma separated string of hyphenated words WITH spaces.
     * @public
     * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *   Hyphenator.addExceptions('de','ziem-lich, Wach-stube');
         *   Hyphenator.run();
         * &lt;/script&gt;
         */
    addExceptions: function (lang, words) {
      if (lang === '') {
        lang = 'global';
      }
      if (exceptions.hasOwnProperty(lang)) {
        exceptions[lang] += ", " + words;
      } else {
        exceptions[lang] = words;
      }
    },
    
    /**
     * @name Hyphenator.hyphenate
       * @public
     * @description
     * Hyphenates the target. The language patterns must be loaded.
     * If the target is a string, the hyphenated string is returned,
     * if it's an object, the values are hyphenated directly.
     * @param {string|Object} target the target to be hyphenated
     * @param {string} lang the language of the target
     * @returns string
     * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
     * &lt;script src = "patterns/en.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
     * var t = Hyphenator.hyphenate('Hyphenation', 'en'); //Hy|phen|ation
     * &lt;/script&gt;
     */
    hyphenate: function (target, lang) {
      var hyphenate, n, i;
      if (Hyphenator.languages.hasOwnProperty(lang)) {
        if (!Hyphenator.languages[lang].prepared) {
          prepareLanguagesObj(lang);
        }
        hyphenate = function (word) {
          if (urlOrMailRE.test(word)) {
            return hyphenateURL(word);
          } else {
            return hyphenateWord(lang, word);
          }
        };
        if (typeof target === 'string' || target.constructor === String) {
          return target.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
        } else if (typeof target === 'object') {
          i = 0;
          while (!!(n = target.childNodes[i++])) {
            if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate!
              n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
            } else if (n.nodeType === 1) {
              if (n.lang !== '') {
                Hyphenator.hyphenate(n, n.lang);
              } else {
                Hyphenator.hyphenate(n, lang);
              }
            }
          }
        }
      } else {
        onError(new Error('Language "' + lang + '" is not loaded.'));
      }
    },
    
    /**
     * @name Hyphenator.getRedPatternSet
       * @description
     * Returns {@link Hyphenator-isBookmarklet}.
     * @param {string} lang the language patterns are stored for
     * @returns object {'patk': pat}
     * @public
         */
    getRedPatternSet: function (lang) {
      return Hyphenator.languages[lang].redPatSet;
    },
    
    /**
     * @name Hyphenator.isBookmarklet
       * @description
     * Returns {@link Hyphenator-isBookmarklet}.
     * @returns boolean
     * @public
         */
    isBookmarklet: function () {
      return isBookmarklet;
    },

    getConfigFromURI: function () {
      var loc = null, re = {}, jsArray = document.getElementsByTagName('script'), i, j, l, s, gp, option;
      for (i = 0, l = jsArray.length; i < l; i++) {
        if (!!jsArray[i].getAttribute('src')) {
          loc = jsArray[i].getAttribute('src');
        }
        if (!loc) {
          continue;
        } else {
          s = loc.indexOf('Hyphenator.js?');
          if (s === -1) {
            continue;
          }
          gp = loc.substring(s + 14).split('&');
          for (j = 0; j < gp.length; j++) {
            option = gp[j].split('=');
            if (option[0] === 'bm') {
              continue;
            }
            if (option[1] === 'true') {
              re[option[0]] = true;
              continue;
            }
            if (option[1] === 'false') {
              re[option[0]] = false;
              continue;
            }
            if (isFinite(option[1])) {
              re[option[0]] = parseInt(option[1], 10);
              continue;
            }
            if (option[0] === 'onhyphenationdonecallback') {
              re[option[0]] = new Function('', option[1]);
              continue;
            }
            re[option[0]] = option[1];
          }
          break;
        }
      }
      return re;
    },

    /**
     * @name Hyphenator.toggleHyphenation
       * @description
     * Checks the current state of the ToggleBox and removes or does hyphenation.
     * @public
         */
    toggleHyphenation: function () {
      if (Hyphenator.doHyphenation) {
        removeHyphenationFromDocument();
        Hyphenator.doHyphenation = false;
        storeConfiguration();
        toggleBox();
      } else {
        hyphenateDocument();
        Hyphenator.doHyphenation = true;
        storeConfiguration();
        toggleBox();
      }
    }
  };
}(window));

//Export properties/methods (for google closure compiler)
Hyphenator['languages'] = Hyphenator.languages;
Hyphenator['config'] = Hyphenator.config;
Hyphenator['run'] = Hyphenator.run;
Hyphenator['addExceptions'] = Hyphenator.addExceptions;
Hyphenator['hyphenate'] = Hyphenator.hyphenate;
Hyphenator['getRedPatternSet'] = Hyphenator.getRedPatternSet;
Hyphenator['isBookmarklet'] = Hyphenator.isBookmarklet;
Hyphenator['getConfigFromURI'] = Hyphenator.getConfigFromURI;
Hyphenator['toggleHyphenation'] = Hyphenator.toggleHyphenation;
window['Hyphenator'] = Hyphenator;

if (Hyphenator.isBookmarklet()) {
  Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', doframes: true});
  Hyphenator.config(Hyphenator.getConfigFromURI());
  Hyphenator.run();
}
