﻿function HangingPunctuation ()
  {
  }
  
(function ()
  {
  /*
  Константы
  */
  var constNoHangingPunctuation = "no-hanging-punctuation";
  var constHangPunct = "hang-punct";
  var constHangSpase = "hang-spase";
  var constHangWbr = "hang-wbr";
  var constGetLinkActionClassName = function (strActionName, strClassName) {return (strActionName +"-"+ strClassName)}; //inner, outer
  
  /*
  Данные
  */
  HangingPunctuation.arSkipTags = [];
  HangingPunctuation.arPunctsTypes = [];
  HangingPunctuation.arPunctsTypesDefault = [];
  HangingPunctuation.PunctStandard;
  HangingPunctuation.PunctZero;
  
  function init ()
    {
    HangingPunctuation.arSkipTags = ["textarea", "pre", "code"];
    
    HangingPunctuation.arPunctsTypes =
      [
      HangingPunctuation.PunctType
        ({
        punct: "«",
        className: "laquo",
        indent: 1,//"0.6em",
        end:
          {
          punct: "»",
          className: "raquo"
          },
        inLink: "inner"
        })
      ,
      HangingPunctuation.PunctType
        ({
        punct: "„",
        className: "ldquo",
        indent: 1,
        end:
          {
          punct: "“",
          className: "rdquo"
          },
        inLink: "inner"
        })
      ,
      HangingPunctuation.PunctType
        ({
        punct: "(",
        className: "lbra",
        indent: 1,//"0.2em",
        end:
          {
          punct: ")",
          className: "rbra"
          },
        inLink: "inner"
        })
      ];
      
    HangingPunctuation.arPunctsTypesDefault =
      [
      HangingPunctuation.arPunctsTypes[0],
      HangingPunctuation.arPunctsTypes[2]
      ];
      
    HangingPunctuation.PunctStandard = HangingPunctuation.PunctType ();
    HangingPunctuation.PunctZero     = HangingPunctuation.PunctType ({indent: "auto"});
    }
  
  /*
  clsPunctType
  */
  function clsPunctType (objParams)
    {
    var objParams = objParams || {};
    
    this.punct;
      if (typeof (objParams) == "string" || objParams instanceof RegExp)
        this.punct = objParams;
      else
        this.punct = objParams.punct;
      
      if (typeof (this.punct) == "string")
        this.punct = new RegExp (this.punct.toRegExp ());
        
    this.className = objParams.className || "";
    
    this.indent = objParams.indent || 1;
      if (typeof (this.indent) == "string" && this.indent.charAt (this.indent.length-1) == "%")
        this.indent = parseFloat (this.indent)/100;
    
    this.end;
    if (objParams.end)
      this.end = HangingPunctuation.PunctType (objParams.end);
    
    this.inLink = objParams.inLink || "none";
    
    this.cash = [];
    }
  (function ()
    {
    clsPunctType.prototype.hang = function ()
      {
      var spnLaquo = this.spnLaquo;
      var spnSpace = this.spnSpace = null;
      var spnWbr   = this.spnWbr = null;
      
      var elParent = spnLaquo.parentNode;
      
      this.prehangDisplay  = spnLaquo.style.display;
      this.prehangPosition = spnLaquo.style.position;
      this.prehangWidth    = spnLaquo.style.width;
      
      /*
      Определяем:
        isInternal = false (кавычка крайняя) --
          кавычка является первым непустым элментом внутри ближайшего родительского блочного узла,
        isInternal = true (кавычка внутренняя) --
          перед ней в ближайшем родительском блоке есть другие непустые элементы.
        Непустыми элментами считаются те, которые содержат какой-либо текст отличный от пробелов.
      Если перед кавычкой и началом родительсокго узла есть узлы,
        то проверяем их пустоту рекурсивно.
      Если кавычка является первой в непосредственном родительском узле, но он не является блочным,
        то рекурсивно проверяем есть ли непустые элементы между родителем и началом родителя родителя,
        и так до тех пор, пока не дойдем до ближайшего блочного родителя.
      По ходу следования удаляем все пробелы,
        так чтобы между кавычкой и ближайшим идущим за ней непустым элментом не было пробелов.
      */
      var reLastSpaces = /(\s|\r|\n)*$/gm;
      var isInternal =
        (function (elBegin)
          {
          if (elBegin == null) return (false);
          
          var elThis = elBegin;
          var elParent = elBegin.parentNode;
          var isInternal = false;
         
          while (elThis)
            {
            if (elThis.nodeType == 3) //Node.TEXT_NODE
              {
              elThis.nodeValue = elThis.nodeValue.replace (reLastSpaces, "");
              isInternal = (elThis.nodeValue != "");
              }
            else if (elThis.nodeType == 1 && (!hasClass (elThis, constHangPunct) && elThis != spnLaquo) && HangingPunctuation.arSkipTags.indexOf (elThis.nodeName.toLowerCase()) == -1) //Node.ELEMENT_NODE
              {
              elThis.innerHTML = (elThis.innerHTML+"").replace (reLastSpaces, "");
              isInternal = arguments.callee (elThis.lastChild);
              }
            
            elThis = elThis.previousSibling;
            
            if (isInternal)
              {
              return (true);
              }
            }
          
          if (getCompStyle (elParent).display != "block")
            {
            isInternal = arguments.callee (elParent.previousSibling);
            }
            
          return (isInternal);
          }
        )(spnLaquo)
      
      /*
      Вычисляем отступ
      */
      var strIndent;
      
      if (typeof (this.indent) == "number")
        {
        var pxEmWidth;
          
          var stlLaquo = getCompStyle (spnLaquo);
          var strCashID = spnLaquo.innerHTML +""+ stlLaquo.fontFamily +""+ stlLaquo.fontWeight +""+ stlLaquo.fontStyle +""+ /*stlLaquo.fontSize +""+*/ stlLaquo.fontVariant;
          if (this.cash [strCashID])
            {
            strIndent = this.cash [strCashID];
            }
          else
            {
            spnLaquo.style.display = "inline-block"; //а не "block", т.к. "block" глючит в Chrome
            spnLaquo.style.position = "absolute"; //Чтобы reflow прошло только по кавычке, а не по всему документу
            spnLaquo.style.width = "1em";
            pxEmWidth = spnLaquo.offsetWidth;
            spnLaquo.style.display = "inline";
            spnLaquo.style.width = "auto";
          var pxLaquoWidth;
            pxLaquoWidth = spnLaquo.offsetWidth;
          var emLaquoWidth = pxLaquoWidth / pxEmWidth;
          
          spnLaquo.style.position = "static";

          strIndent = this.cash [strCashID] = (emLaquoWidth * this.indent) +"em";
          this.cash.length++;
          }
        }
      else if (typeof (this.indent) == "string")
        {
        strIndent = this.indent;
        }
      
      /*
      Устанавливаем отступ для кавычки
      */
      spnLaquo.style.marginLeft = "-"+ strIndent;
      if (navigator.isOpera ()) spnLaquo.style.display = "inline-block";
      //spnLaquo.style.visibility = "visible";
      
      /*
      Если кавычка внутряння,
      добавляем корректирующий пробел
      */
      if (isInternal)
        {
        spnSpace = this.spnSpace = document.createElement ("span");
          spnSpace.className = constHangSpase;
          spnSpace.style.display = "inline";
          spnSpace.style.marginRight = strIndent;
          spnSpace.appendChild (document.createTextNode (" "))
        elParent.insertBefore (spnSpace, spnLaquo);
        
        if (navigator.isIE () && navigator.isOpera ())
          {
          spnWbr = this.spnWbr = document.createElement ("wbr");
            spnWbr.className = constHangWbr;
          elParent.insertBefore (spnWbr, spnSpace);
          }
        }
      
      /*
      Исправляем глюк:
        обрезаются висячая пунктуация
      Возникает:
        в IE в режиме xHTML
      */    
      if (navigator.isIE ())
        {
        var elBlock = spnLaquo;
        while (getCompStyle (elBlock).display != "block") elBlock = elBlock.parentNode;
        
        if (elBlock.nodeName != "LI" && elBlock.nodeName != "OL" && elBlock.nodeName != "UL")
          {
          elBlock.style.paddingLeft = strIndent;
          elBlock.style.marginLeft = "-"+ strIndent;
          }
        }
      
      /*
      Исправляем глюк:
        пропадает висячая пунктуация до первой перерисовки страницы
      Возникает:
        в Opera
      */
      //Искуственно запускаем перерисовку страницы
      if (navigator.isOpera ()) doReflow (spnLaquo);
        
      this.isHanged = true;
      }
      
    
    /*
    Ссылки
    */
    var hshInLinkConditions =
      {
      "inner":
        {
        getLink: function (elLaquo, objPunct)
          {
          if (
             elLaquo.parentNode.nodeName == "A" &&
             getFirstNonEmptyChild (elLaquo.parentNode) == elLaquo &&
             objPunct.end && hasClass (getLastNonEmptyChild (elLaquo.parentNode), objPunct.end.prototype.className)
             )
             return (elLaquo.parentNode);
          },
        actions:
          {
          "inner": function (elLaquo, objPunct, aLink)
            {
            var elRaquo = getPathToPreviousNonEmptyNode (aLink.lastChild).pop();
            var elUnderline = document.createElement ("span");
            aLink.insertBefore (elUnderline, elRaquo);
            var elThis = elLaquo.nextSibling;
            while (elThis != elUnderline)
              {
              elNext = elThis.nextSibling;
              elUnderline.appendChild (elThis);
              elThis = elNext;
              }
              
            objPunct._linkProperties.prelinkTextDecoration = elUnderline.style.textDecoration;
            elUnderline.style.textDecoration = getCompStyle (aLink).textDecoration;
            aLink.style.textDecoration = "none";
            
            objPunct._linkProperties.elUnderline = elUnderline;
            objPunct._linkProperties.elRaquo = elRaquo;
            }
          ,
          "outer": function (elLaquo, objPunct, aLink)
            {
            aLink.parentNode.insertBefore (elLaquo, aLink);
            if (objPunct.spnSpace) aLink.parentNode.insertBefore (objPunct.spnSpace, elLaquo);
            if (objPunct.spnWbr) aLink.parentNode.insertBefore (objPunct.spnWbr, objPunct.spnSpace);
            
            var elRaquo = getLastNonEmptyChild (aLink);
            aLink.parentNode.appendChild (elRaquo);
            if (elLaquo.nextSibling.nextSibling) aLink.parentNode.insertBefore (elRaquo, elLaquo.nextSibling.nextSibling);
            
            objPunct._linkProperties.elRaquo = elRaquo;
            }
          },
        unactions:
          {
          "inner": function (elLaquo, objPunct)
            {
            //если кавычка не отвешена, то выполняется с ошибкой: пробел перед кавычкой подчеркивается
            //alert ("inner -> inner");
            
            var aLink = objPunct._linkProperties.aLink;
            var elUnderline = objPunct._linkProperties.elUnderline;
            var elLaquo = objPunct._linkProperties.elLaquo;
            var elRaquo = objPunct._linkProperties.elRaquo;
            
            while (elUnderline.firstChild)
              {
              aLink.insertBefore (elUnderline.firstChild, elRaquo)
              }
            aLink.removeChild (elUnderline);
            
            aLink.style.textDecoration = objPunct._linkProperties.prelinkTextDecoration;
            }
          ,
          "outer": function (elLaquo, objPunct)
            {
            //alert ("outer -> inner");
            //если кавычка не отвешена, то выполняется с ошибкой: пробел перед кавычкой подчеркивается
            
            var aLink = objPunct._linkProperties.aLink;
            var elLaquo = objPunct._linkProperties.elLaquo;
            var elRaquo = objPunct._linkProperties.elRaquo;
            
            aLink.insertBefore (elLaquo, aLink.firstChild);
            if (objPunct.isHanged && objPunct.spnSpace) aLink.insertBefore (objPunct.spnSpace, elLaquo);
            if (objPunct.isHanged && objPunct.spnWbr) aLink.insertBefore (objPunct.spnWbr, objPunct.spnSpace);
            aLink.appendChild (elRaquo);
            
            //alert (objPunct._linkProperties.aLink.parentNode);
            //aLink.parentNode.insertBefore (elLaquo, aLink);
            }
          }
        }
      ,
      "outer":
        {
        getLink: function (elLaquo, objPunct)
          {
          if (
               (getPathToNextNonEmptyNode (elLaquo.nextSibling).pop() || {}).nodeName == "A" &&
                objPunct.end && hasClass (getPathToNextNonEmptyNode (getPathToNextNonEmptyNode (elLaquo.nextSibling).pop().nextSibling).pop(), objPunct.end.prototype.className)
             )
             return (getPathToNextNonEmptyNode (elLaquo.nextSibling).pop());
          },
        actions:
          {
          "inner": function (elLaquo, objPunct, aLink)
            {
            aLink.insertBefore (elLaquo, aLink.firstChild);
            if (objPunct.spnSpace) aLink.insertBefore (objPunct.spnSpace, elLaquo);
            if (objPunct.spnWbr) aLink.insertBefore (objPunct.spnWbr, objPunct.spnSpace);
            
            aLink.appendChild (getPathToNextNonEmptyNode (aLink.nextSibling).pop());
            
            hshInLinkConditions ["inner"].actions ["inner"](elLaquo, objPunct, aLink);
            }
          ,
          "outer": function (elLaquo, objPunct, aLink)
            {
            }
          },
        unactions:
          {
          "inner": function (elLaquo, objPunct)
            {
            //выполяняется без ошибок, даже если кавычка не отвешена
            //alert ("inner -> outer");
            
            var aLink = objPunct._linkProperties.aLink;
            var elLaquo = objPunct._linkProperties.elLaquo;
            var elRaquo = objPunct._linkProperties.elRaquo;
            var elParent = aLink.parentNode;
            
            hshInLinkConditions ["inner"].unactions["inner"] (elLaquo, objPunct);
            
            elParent.insertBefore (elLaquo, aLink);
            if (aLink.nextSibling) 
              elParent.insertBefore (elRaquo, aLink.nextSibling);
            else
              elParent.appendChild (elRaquo);
              
            if (objPunct.isHanged && objPunct.spnSpace) elParent.insertBefore (objPunct.spnSpace, elLaquo);
            if (objPunct.isHanged && objPunct.spnWbr)   elParent.insertBefore (objPunct.spnWbr, objPunct.spnSpace);
            }
          ,
          "outer": function (elLaquo, objPunct)
            {
            //alert ("outer -> outer");
            }
          }
        }
      }
        
    clsPunctType.prototype.link = function ()
      {
      var spnLaquo = this.spnLaquo;
      var objPunct = spnLaquo._punct;
      this._linkProperties = {isLinked: false};
      //alert (objPunct._linkProperties);
      //objPunct._linkProperties = {};
      
      /*
      Определяем, относится ли кавычка к ссылке,
      и какое положение относительно ее она занимает (objCondition)
      */
      var objCondition;
      var strConditionName;
      var aLink;
      for (var strThisConditionName in hshInLinkConditions)
        {
        var objThisCondition = hshInLinkConditions [strThisConditionName];
        aLink = objThisCondition.getLink (spnLaquo, objPunct);
        if (aLink)
          {
          objCondition = objThisCondition;
          strConditionName = strThisConditionName;
          break;
          }
        }
      
      /*
      Если связан со ссылкой,
      то ищем и выполняем соответствующее ее положению действие
      */
      if (aLink)
        {
        /*
        Ищем действие, которое можно совершить.
        Действие иберем CSS-класов ссылки,
        а если его там нет, то берем действие по-умолчанию для данной кавычки (objPunct.inLink)
        */
        var strInLinkActionName;
        for (var strActionName in objThisCondition.actions)
          {
          if (hasClass (aLink, constGetLinkActionClassName(strActionName,objPunct.className) ))
            {
            strInLinkActionName = strActionName;
            break;
            }
          }
       
        strInLinkActionName = strInLinkActionName || objPunct.inLink;
        
        /*
        Если действие надено,
        то вполняем его
        */
        if (strInLinkActionName != "none" && typeof (objThisCondition.actions [strInLinkActionName]) == "function")
          {
          objThisCondition.actions [strInLinkActionName] (spnLaquo, objPunct, aLink);
          
          objPunct._linkProperties.isLinked = true;
          objPunct._linkProperties.strFrom  = strConditionName;
          objPunct._linkProperties.strTo    = strInLinkActionName;
          objPunct._linkProperties.aLink    = aLink;
          objPunct._linkProperties.elLaquo  = spnLaquo;
          
          //alert (objPunct._linkProperties.strFrom +"\r\n"+ objPunct._linkProperties.strTo);
          }
        }
      }
      
    clsPunctType.prototype.unlink = function ()
      {
      var spnLaquo = this.spnLaquo;
      var objPunct = this;
      var objLinkProperties = this._linkProperties;
      
      hshInLinkConditions [objPunct._linkProperties.strFrom].unactions [objPunct._linkProperties.strTo](spnLaquo, objPunct)
      }
      
    clsPunctType.prototype.unhang = function ()
      {
      //alert (document.body.innerHTML);
      var spnLaquo = this.spnLaquo;
      var elParent = spnLaquo.parentNode;
      
      if (this.spnSpace) elParent.removeChild (this.spnSpace);
      if (this.spnWbr && this.spnWbr.parentNode)   this.spnWbr.parentNode.removeChild (this.spnWbr);
      
      //spnLaquo.style.visibility = "hidden";
      //if (this.spnWbr)   elParent.removeChild (this.spnWbr);
      
      //elParent.insertBefore (document.createTextNode (" "), spnLaquo);
      //if (spnLaquo._laquoProperties.spnSpace) spnLaquo.innerHTML = " "+ spnLaquo.innerHTML;
      
      //Если кавычка находится в начале ссылки, то помещаем пробел не непосредственно перед кавычкой, а перед ссылкой
      var elSpaceNextSibling = spnLaquo; if (this._linkProperties && this._linkProperties.isLinked) elSpaceNextSibling = this._linkProperties.aLink;
      //Помещаем пробел перед кавычкой (или ссылкой)
      if (this.spnSpace) insertTextNodeBefore (" ", elSpaceNextSibling);
      
      spnLaquo.style.marginLeft = this.prehangMarginLef || "";

      if (this.prehangDisplay)   spnLaquo.style.display    = this.prehangDisplay;
      if (this.prehangPosition)  spnLaquo.style.position   = this.prehangPosition;
      if (this.prehangWidth)     spnLaquo.style.width      = this.prehangWidth;
      
      //Искуственно запускаем перерисовку страницы для устранения глюка в Opera
      if (navigator.isOpera ()) doReflow (elParent);
      
      this.isHanged = false;
      }
      
    })()
  
  /*
  PunctType
  */
  HangingPunctuation.PunctType = function (strPunctType)
    {
    function Punct (spnLaquo)
      {
      this.spnLaquo = spnLaquo;
      spnLaquo._punct = this;
      spnLaquo.className += " "+ constHangPunct +" "+ this.className;
      //this._linkProperties = {xxx: "xxx"};
      this.isHanged = false;
      }
    Punct.prototype = new clsPunctType (strPunctType);
    
    function f (spnLaquo)
      {
      if (spnLaquo._punct && spnLaquo._punct instanceof Punct)
        return (spnLaquo._punct);
      else
        return new Punct (spnLaquo);
      }
    f.prototype = Punct.prototype;
    return (f);
    }
  
  /*
  
  */
  HangingPunctuation.typo = function (arelRoots, arPunctTypes)
    {
    if (!(arelRoots instanceof Array)) arelRoots = [arelRoots];
    var arPunctTypes = arPunctTypes || HangingPunctuation.arPunctsTypesDefault;
    
    var arPuncts = [];
    for (var intRootNumber = 0; intRootNumber < arelRoots.length; intRootNumber++)
      {
      var elThisRoot = arelRoots [intRootNumber];
      
      for (var intPunctNumber = 0; intPunctNumber < arPunctTypes.length; intPunctNumber++)
        {
        var PunctTypeThis = arPunctTypes [intPunctNumber];
        arPuncts = arPuncts.concat
          (
          mapTextnodes (elThisRoot, PunctTypeThis).reverse ()
          );
        
        if (PunctTypeThis.prototype.end)
          {
          mapTextnodes (elThisRoot, PunctTypeThis.prototype.end);
          }
        }
        
      elThisRoot.normalize ();
      }
    
    for (var intPunctNumber = 0; intPunctNumber < arPuncts.length; intPunctNumber++)
      {
      arPuncts [intPunctNumber].hang ();
      }
    
    for (var intPunctNumber = 0; intPunctNumber < arPuncts.length; intPunctNumber++)
      {
      arPuncts [intPunctNumber].link ();
      }
    }
  
  function mapTextnodes (elRoot, PunctTypeThis)
    {
    var arPuncts = [];
    
    (function (elRoot)
      {
      var elThis = elRoot.firstChild;
      while (elThis)
        {
        if (HangingPunctuation.arSkipTags.indexOf (elThis.nodeName.toLowerCase()) == -1 && !hasClass(elThis, constNoHangingPunctuation) && !hasClass(elThis, constHangPunct))
          {
          if (elThis.nodeType == 3)
            {
            var objPunct = wrapElement (elThis, PunctTypeThis);
            if (objPunct)
              {
              arPuncts [arPuncts.length] = objPunct;
              elThis = elThis.nextSibling;
              }
            }
          else if (elThis.nodeType == 1)
            {
            arguments.callee (elThis)
            }
          }
        elThis = elThis.nextSibling;
        }
      })(elRoot)
      
    return (arPuncts);
    }
    
  function wrapElement (tnTextNode, PunctTypeThis)
    {
    PunctTypeThis;
      var rePunct = PunctTypeThis.prototype.punct;
      var strClassName = PunctTypeThis.prototype.className;
    var strElement = tnTextNode.nodeValue.match (rePunct);
    var intStrElementIndex = tnTextNode.nodeValue.search (rePunct);
    
    if (intStrElementIndex > -1)
      {
      var tnElement = tnTextNode.splitText (intStrElementIndex);
      var elNext = tnTextNode.nextSibling;
      var tnEnd = tnElement.splitText (strElement.length);
      
      var elElement = document.createElement ("span");
      var objThisPunct = PunctTypeThis (elElement);
      elElement.appendChild (tnElement);
      
      if (tnEnd.length)
        {
        tnEnd.parentNode.insertBefore (elElement, tnEnd);
        }
      else //Глюк IE
        {
        tnTextNode.parentNode.insertBefore (elElement, tnTextNode.nextSibling);
        }
      
      return (objThisPunct);
      }
    }
    
    
  HangingPunctuation.untypo = function (arelRoots, arPunctTypes)
    {
    if (!(arelRoots instanceof Array)) arelRoots = [arelRoots];
    var arPunctTypes = arPunctTypes || HangingPunctuation.arPunctsTypesDefault;
    
    var arPuncts = [];
    for (var intRootNumber = 0; intRootNumber < arelRoots.length; intRootNumber++)
      {
      var elThisRoot = arelRoots [intRootNumber];
      
      for (var intPunctNumber = 0; intPunctNumber < arPunctTypes.length; intPunctNumber++)
        {
        var PunctTypeThis = arPunctTypes [intPunctNumber];
        arPuncts = arPuncts.concat
          (
          getElementsByClassName (elThisRoot, constHangPunct +" "+ PunctTypeThis.prototype.className)
          );
        
        if (PunctTypeThis.prototype.end)
          {
          arPuncts = arPuncts.concat
            (
            getElementsByClassName (elThisRoot, constHangPunct +" "+ PunctTypeThis.prototype.end.prototype.className)
            );
          }
        }
        
      elThisRoot.normalize ();
      }
    
    //alert (arElements.length);
    for (var intPunctNumber = 0; intPunctNumber < arPuncts.length; intPunctNumber++)
      {
      //this.hang (arElements [intElementNumber]);
      //alert (arElements [intElementNumber]);
      //arElements [intElementNumber].style.border = "1px solid red";
      arPuncts [intPunctNumber]._punct.unhang ();
      if (arPuncts [intPunctNumber]._punct._linkProperties && arPuncts [intPunctNumber]._punct._linkProperties.isLinked) arPuncts [intPunctNumber]._punct.unlink ();
      unwrapElement (arPuncts [intPunctNumber]);
      }
    
    document.body.normalize ();
    }
    
  function unwrapElement (spnLaquo)
    {
    //document.body.normalize ();
    //alert ("ДО: "+ document.body.innerHTML)
    
    //spnLaquo._punct.unhang ();

    //document.body.normalize ();
    //alert ("1: "+ document.body.innerHTML)
    
    var elParent = spnLaquo.parentNode;
    insertTextNodeBefore (spnLaquo.innerHTML, spnLaquo);
    
    //alert ("2: "+ spnLaquo.parentNode.innerHTML +"\r\n\r\n"+ elParent.tagName +"\r\n\r\n"+ document.body.innerHTML)

    elParent.removeChild (spnLaquo);

    //document.body.normalize ();
    }
  
    
  /*
  UTILITIES
  */
  var isReflowStarted = false;
  function doReflow (elElement)
    {
    /*
    Эта функция перерисовывает окно
    */
    elElement = elElement || document.body;
    if (!isReflowStarted)
      {
      window.setTimeout (function ()
        {
        var strDisplay = elElement.style.display;
        elElement.style.display = "none";
        elElement.offsetWidth;
        elElement.style.display = strDisplay;
        isReflowStarted = false;
        }, 0)
      }
    isReflowStarted = true;
    }
  
  function getCompStyle (elElement)
    {
    if (document.defaultView)
      return (document.defaultView.getComputedStyle(elElement, ""));
    else if (elElement.currentStyle)  
      return (elElement.currentStyle);
    }
    
  function getInnerText (elElement)
    {
    return (elElement.textContent || elElement.innerText);
    }
  
  function getPathToNonEmptyNode (elStart, fncNextElement)
    {
    var arelPath = [];
    var elThis = elStart;
    while (elThis)
      {
      arelPath [arelPath.length] = elThis;
      if ((elThis.nodeValue || getInnerText (elThis) || "").replace (/[\s\t\n\r]/gm, "") != "")
        {
        return (arelPath);
        }
      elThis = fncNextElement (elThis);
      }
    return ([]);
    }
  function getPathToNextNonEmptyNode (elStart)
    {
    return (getPathToNonEmptyNode (elStart, function (el) {return (el.nextSibling)}));
    }
  function getPathToPreviousNonEmptyNode (elStart)
    {
    return (getPathToNonEmptyNode (elStart, function (el) {return (el.previousSibling)}));
    }
  function getFirstNonEmptyChild (elRoot)
    {
    var arPath = getPathToNextNonEmptyNode (elRoot.firstChild);
    if (arPath.length > 0)
      return (arPath.pop());
    else
      return;
    }
  function getLastNonEmptyChild (elRoot)
    {
    var arPath = getPathToPreviousNonEmptyNode (elRoot.lastChild);
    if (arPath.length > 0)
      return (arPath.pop());
    else
      return;
    }

  function getElementsByClassName (elRootElement, strClassName)
    {
    var arResult = [];
    var reClass = new RegExp("\\b"+ strClassName +"\\b");
    var arElements = elRootElement.getElementsByTagName ("*");
    for (var i = 0; i < arElements.length; i++)
      {
      var strClasses = arElements[i].className;
      if (reClass.test(strClasses)) arResult.push(arElements[i]);
      }
    return arResult;
    }; 
    
  function hasClass(ele,cls)
    {
    return (ele.className || "").match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
    }
  function addClass(ele,cls)
    {
    if (!this.hasClass(ele,cls)) ele.className += " "+cls;
    }
  function removeClass(ele,cls)
    {
    if (hasClass(ele,cls))
      {
      var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
      ele.className=ele.className.replace(reg,' ');
      }
    }
  
  function insertTextNodeBefore (strTextNodeValue, elTargetElement)
    {
    /*
    Эта функция создает TextNode и помещает его перед элементов
    Эта функция нужна из-за того, что при нормальном способе:
      В IE 8 в режиме xHTML происходит глюк -
      странным образом дублируется elTargetElement.parentNode и реальный parentNode помещается внутрь продублированного 
    Параметры:
      strTextNodeValue - TextNode;
      elTargetElement - элемент, перед которым его помещать
    */
    //Если нормальный браузер,
    if (!navigator.isIE()) //то делаем все как обычно
      {
      elTargetElement.parentNode.insertBefore (document.createTextNode (strTextNodeValue), elTargetElement);
      }
    //Если IE
    else
      {
      //Создаем SPAN место TextNode
      var spnContainer = document.createElement ("SPAN");
      spnContainer.appendChild (document.createTextNode (strTextNodeValue));
      //spnContainer.innerHTML = strTextNodeValue;
      
      elTargetElement.parentNode.insertBefore (spnContainer, elTargetElement);
      
      //А затем очищаем его от самого тега
      //Очищаем только если он содержит не только пробелы, иначе уничтожится...
      if (spnContainer.innerHTML.replace (/(\s|\r|\n)*/gm, "") != "") spnContainer.outerHTML = spnContainer.innerHTML

      document.body.normalize ();
      }
    }
  
  String.prototype.toRegExp = function ()
    {
    var txtRegExp = "";
    var arSymbols = this.split ("");
    for (var intSymbolNumber = 0; intSymbolNumber < arSymbols.length; intSymbolNumber++)
      {
      var txtThisSymbol = arSymbols [intSymbolNumber];
      var txtThisSymbolCode12 = txtThisSymbol.charCodeAt (0).toString (16);
      while (txtThisSymbolCode12.length < 4) txtThisSymbolCode12 = "0"+ txtThisSymbolCode12;
      txtRegExp += "\\u"+ txtThisSymbolCode12;
      }
    return (txtRegExp);
    }
    
  Array.prototype.indexOf = function (element)
    {
    for (var i = 0; i < this.length; i++)
      {
      if (this[i] == element) return (i)
      }
    return (-1)
    }
    
  navigator.isIE = function ()
    {
    return (navigator.userAgent.search('MSIE') != -1);
    }
  navigator.isOpera = function ()
    {
    return (navigator.userAgent.search('Opera') != -1);
    }
  navigator.getOperaVersion = function ()
    {
    var isOpera = /Opera[\/\s](\d+\.\d+)/.test (navigator.userAgent)
    if (!isOpera)
      return (-1);
    else
      return (new Number(RegExp.$1));
    }
    
  init ();
  })()
