text: ;
  (function(window, $) {
      init = function() {
              $('body').each(function() {
                  var hanzi = '[\u2E80-\u2FFF\u31C0-\u31EF\u3300-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFE30-\uFE4F]',
                      punc = {
                          base: "[@&=_\\$%\\^\\*-\\+/]",
                          open: "[\\(\\[\\{<‘“]",
                          close: "[,\\.\\?!:\\)\\]\\}>’”]"
                      },
                      latin = '[A-Za-z0-9\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u1E00-\u1EFF]' + '|' + punc.base,
                      patterns = ['/(' + hanzi + ')(' + latin + '|' + punc.open + ')/ig', '/(' + latin + '|' + punc.close + ')(' + hanzi + ')/ig'];
  
                  patterns.forEach(function(exp) {
                          findAndReplaceDOMText(this, {
                              find: eval(exp),
                              replace: '$1<hanla>$2'
                          })
                      },
                      this);
  
                  findAndReplaceDOMText(this, {
                      find: '<hanla>',
                      replace: function() {
                          return document.createElement('hanla')
                      }
                  });
  
                  this.normalize();
  
                  $('* > hanla:first-child').parent().each(function() {
                      if (this.firstChild.nodeType == 1) {
                          $(this).before($('<hanla/>'));
                          $(this).find('hanla:first-child').remove();
                      }
                  });
              })
          },
  
          findAndReplaceDOMText = function(a, b) {
              var b = b;
  
              b.filterElements = function(el) {
                  var name = el.nodeName.toLowerCase(),
                      classes = (el.nodeType == 1) ? el.getAttribute('class') : '',
                      charized = (classes && classes.match(/han-js-charized/) != null) ? true : false;
  
                  return name !== 'style' && name !== 'script' && !charized;
              }
  
              return window.findAndReplaceDOMText(a, b);
          }
  
      $(document).ready(
          function() {
              init();
          });
  })(window, window.jQuery, undefined);
  
  /**
   * findAndReplaceDOMText v 0.4.6
   * @author James Padolsey http://james.padolsey.com
   * @license http://unlicense.org/UNLICENSE
   *
   * Matches the text of a DOM node against a regular expression
   * and replaces each match (or node-separated portions of the match)
   * in the specified element.
   */
  (function (root, factory) {
      window.findAndReplaceDOMText = factory();
  }(this, function factory() {
  
     var PORTION_MODE_RETAIN = 'retain';
     var PORTION_MODE_FIRST = 'first';
  
     var doc = document;
     var hasOwn = {}.hasOwnProperty;
  
     function escapeRegExp(s) {
         return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
     }
  
     function exposed() {
         // Try deprecated arg signature first:
         return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments);
     }
  
     function deprecated(regex, node, replacement, captureGroup, elFilter) {
         if ((node && !node.nodeType) && arguments.length <= 2) {
             return false;
         }
         var isReplacementFunction = typeof replacement == 'function';
  
         if (isReplacementFunction) {
             replacement = (function(original) {
                 return function(portion, match) {
                     return original(portion.text, match.startIndex);
                 };
             }(replacement));
         }
  
         // Awkward support for deprecated argument signature (<0.4.0)
         var instance = findAndReplaceDOMText(node, {
  
             find: regex,
  
             wrap: isReplacementFunction ? null : replacement,
             replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
  
             prepMatch: function(m, mi) {
  
                 // Support captureGroup (a deprecated feature)
  
                 if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
  
                 if (captureGroup > 0) {
                     var cg = m[captureGroup];
                     m.index += m[0].indexOf(cg);
                     m[0] = cg;
                 }
  
                 m.endIndex = m.index + m[0].length;
                 m.startIndex = m.index;
                 m.index = mi;
  
                 return m;
             },
             filterElements: elFilter
         });
  
         exposed.revert = function() {
             return instance.revert();
         };
  
         return true;
     }
  
     /**
      * findAndReplaceDOMText
      *
      * Locates matches and replaces with replacementNode
      *
      * @param {Node} node Element or Text node to search within
      * @param {RegExp} options.find The regular expression to match
      * @param {String|Element} [options.wrap] A NodeName, or a Node to clone
      * @param {String} [options.wrapClass] A classname to append to the wrapping element
      * @param {String|Function} [options.replace='$&'] What to replace each match with
      * @param {Function} [options.filterElements] A Function to be called to check whether to
      *	process an element. (returning true = process element,
      *	returning false = avoid element)
      */
     function findAndReplaceDOMText(node, options) {
         return new Finder(node, options);
     }
  
     exposed.NON_PROSE_ELEMENTS = {
         br:1, hr:1,
         // Media / Source elements:
         script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
         // Input elements
         input:1, textarea:1, select:1, option:1, optgroup: 1, button:1
     };
  
     exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = {
  
         // Elements that will not contain prose or block elements where we don't
         // want prose to be matches across element borders:
  
         // Block Elements
         address:1, article:1, aside:1, blockquote:1, dd:1, div:1,
         dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1,
         h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1,
         output:1, p:1, pre:1, section:1, ul:1,
         // Other misc. elements that are not part of continuous inline prose:
         br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1,
         // Media / Source elements:
         script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
         // Input elements
         input:1, textarea:1, select:1, option:1, optgroup:1, button:1,
         // Table related elements:
         table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1
  
     };
  
     exposed.NON_INLINE_PROSE = function(el) {
         return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase());
     };
  
     // Presets accessed via `options.preset` when calling findAndReplaceDOMText():
     exposed.PRESETS = {
         prose: {
             forceContext: exposed.NON_INLINE_PROSE,
             filterElements: function(el) {
                 return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase());
             }
         }
     };
  
     exposed.Finder = Finder;
  
     /**
      * Finder -- encapsulates logic to find and replace.
      */
     function Finder(node, options) {
  
         var preset = options.preset && exposed.PRESETS[options.preset];
  
         options.portionMode = options.portionMode || PORTION_MODE_RETAIN;
  
         if (preset) {
             for (var i in preset) {
                 if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) {
                     options[i] = preset[i];
                 }
             }
         }
  
         this.node = node;
         this.options = options;
  
         // Enable match-preparation method to be passed as option:
         this.prepMatch = options.prepMatch || this.prepMatch;
  
         this.reverts = [];
  
         this.matches = this.search();
  
         if (this.matches.length) {
             this.processMatches();
         }
  
     }
  
     Finder.prototype = {
  
         /**
          * Searches for all matches that comply with the instance's 'match' option
          */
         search: function() {
  
             var match;
             var matchIndex = 0;
             var offset = 0;
             var regex = this.options.find;
             var textAggregation = this.getAggregateText();
             var matches = [];
             var self = this;
  
             regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex;
  
             matchAggregation(textAggregation);
  
             function matchAggregation(textAggregation) {
                 for (var i = 0, l = textAggregation.length; i < l; ++i) {
  
                     var text = textAggregation[i];
  
                     if (typeof text !== 'string') {
                         // Deal with nested contexts: (recursive)
                         matchAggregation(text);
                         continue;
                     }
  
                     if (regex.global) {
                         while (match = regex.exec(text)) {
                             matches.push(self.prepMatch(match, matchIndex++, offset));
                         }
                     } else {
                         if (match = text.match(regex)) {
                             matches.push(self.prepMatch(match, 0, offset));
                         }
                     }
  
                     offset += text.length;
                 }
             }
  
             return matches;
  
         },
  
         /**
          * Prepares a single match with useful meta info:
          */
         prepMatch: function(match, matchIndex, characterOffset) {
  
             if (!match[0]) {
                 throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
             }
  
             match.endIndex = characterOffset + match.index + match[0].length;
             match.startIndex = characterOffset + match.index;
             match.index = matchIndex;
  
             return match;
         },
  
         /**
          * Gets aggregate text within subject node
          */
         getAggregateText: function() {
  
             var elementFilter = this.options.filterElements;
             var forceContext = this.options.forceContext;
  
             return getText(this.node);
  
             /**
              * Gets aggregate text of a node without resorting
              * to broken innerText/textContent
              */
             function getText(node) {
  
                 if (node.nodeType === Node.TEXT_NODE) {
                     return [node.data];
                 }
  
                 if (elementFilter && !elementFilter(node)) {
                     return [];
                 }
  
                 var txt = [''];
                 var i = 0;
  
                 if (node = node.firstChild) do {
  
                     if (node.nodeType === Node.TEXT_NODE) {
                         txt[i] += node.data;
                         continue;
                     }
  
                     var innerText = getText(node);
  
                     if (
                         forceContext &&
                         node.nodeType === Node.ELEMENT_NODE &&
                         (forceContext === true || forceContext(node))
                     ) {
                         txt[++i] = innerText;
                         txt[++i] = '';
                     } else {
                         if (typeof innerText[0] === 'string') {
                             // Bridge nested text-node data so that they're
                             // not considered their own contexts:
                             // I.e. ['some', ['thing']] -> ['something']
                             txt[i] += innerText.shift();
                         }
                         if (innerText.length) {
                             txt[++i] = innerText;
                             txt[++i] = '';
                         }
                     }
                 } while (node = node.nextSibling);
  
                 return txt;
  
             }
  
         },
  
         /**
          * Steps through the target node, looking for matches, and
          * calling replaceFn when a match is found.
          */
         processMatches: function() {
  
             var matches = this.matches;
             var node = this.node;
             var elementFilter = this.options.filterElements;
  
             var startPortion,
                 endPortion,
                 innerPortions = [],
                 curNode = node,
                 match = matches.shift(),
                 atIndex = 0, // i.e. nodeAtIndex
                 matchIndex = 0,
                 portionIndex = 0,
                 doAvoidNode,
                 nodeStack = [node];
  
             out: while (true) {
  
                 if (curNode.nodeType === Node.TEXT_NODE) {
  
                     if (!endPortion && curNode.length + atIndex >= match.endIndex) {
                         // We've found the ending
                         // (Note that, in the case of a single portion, it'll be an
                         // endPortion, not a startPortion.)
                         endPortion = {
                             node: curNode,
                             index: portionIndex++,
                             text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
  
                             // If it's the first match (atIndex==0) we should just return 0
                             indexInMatch: atIndex === 0 ? 0 : atIndex - match.startIndex,
  
                             indexInNode: match.startIndex - atIndex,
                             endIndexInNode: match.endIndex - atIndex,
                             isEnd: true
                         };
  
                     } else if (startPortion) {
                         // Intersecting node
                         innerPortions.push({
                             node: curNode,
                             index: portionIndex++,
                             text: curNode.data,
                             indexInMatch: atIndex - match.startIndex,
                             indexInNode: 0 // always zero for inner-portions
                         });
                     }
  
                     if (!startPortion && curNode.length + atIndex > match.startIndex) {
                         // We've found the match start
                         startPortion = {
                             node: curNode,
                             index: portionIndex++,
                             indexInMatch: 0,
                             indexInNode: match.startIndex - atIndex,
                             endIndexInNode: match.endIndex - atIndex,
                             text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
                         };
                     }
  
                     atIndex += curNode.data.length;
  
                 }
  
                 doAvoidNode = curNode.nodeType === Node.ELEMENT_NODE && elementFilter && !elementFilter(curNode);
  
                 if (startPortion && endPortion) {
  
                     curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion);
  
                     // processMatches has to return the node that replaced the endNode
                     // and then we step back so we can continue from the end of the
                     // match:
  
                     atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode);
  
                     startPortion = null;
                     endPortion = null;
                     innerPortions = [];
                     match = matches.shift();
                     portionIndex = 0;
                     matchIndex++;
  
                     if (!match) {
                         break; // no more matches
                     }
  
                 } else if (
                     !doAvoidNode &&
                     (curNode.firstChild || curNode.nextSibling)
                 ) {
                     // Move down or forward:
                     if (curNode.firstChild) {
                         nodeStack.push(curNode);
                         curNode = curNode.firstChild;
                     } else {
                         curNode = curNode.nextSibling;
                     }
                     continue;
                 }
  
                 // Move forward or up:
                 while (true) {
                     if (curNode.nextSibling) {
                         curNode = curNode.nextSibling;
                         break;
                     }
                     curNode = nodeStack.pop();
                     if (curNode === node) {
                         break out;
                     }
                 }
  
             }
  
         },
  
         /**
          * Reverts ... TODO
          */
         revert: function() {
             // Reversion occurs backwards so as to avoid nodes subsequently
             // replaced during the matching phase (a forward process):
             for (var l = this.reverts.length; l--;) {
                 this.reverts[l]();
             }
             this.reverts = [];
         },
  
         prepareReplacementString: function(string, portion, match) {
             var portionMode = this.options.portionMode;
             if (
                 portionMode === PORTION_MODE_FIRST &&
                 portion.indexInMatch > 0
             ) {
                 return '';
             }
             string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
                 var replacement;
                 switch(t) {
                     case '&':
                         replacement = match[0];
                         break;
                     case '`':
                         replacement = match.input.substring(0, match.startIndex);
                         break;
                     case '\'':
                         replacement = match.input.substring(match.endIndex);
                         break;
                     default:
                         replacement = match[+t] || '';
                 }
                 return replacement;
             });
  
             if (portionMode === PORTION_MODE_FIRST) {
                 return string;
             }
  
             if (portion.isEnd) {
                 return string.substring(portion.indexInMatch);
             }
  
             return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length);
         },
  
         getPortionReplacementNode: function(portion, match) {
  
             var replacement = this.options.replace || '$&';
             var wrapper = this.options.wrap;
             var wrapperClass = this.options.wrapClass;
  
             if (wrapper && wrapper.nodeType) {
                 // Wrapper has been provided as a stencil-node for us to clone:
                 var clone = doc.createElement('div');
                 clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper);
                 wrapper = clone.firstChild;
             }
  
             if (typeof replacement == 'function') {
                 replacement = replacement(portion, match);
                 if (replacement && replacement.nodeType) {
                     return replacement;
                 }
                 return doc.createTextNode(String(replacement));
             }
  
             var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper;
  
              if (el && wrapperClass) {
                 el.className = wrapperClass;
             }
  
             replacement = doc.createTextNode(
                 this.prepareReplacementString(
                     replacement, portion, match
                 )
             );
  
             if (!replacement.data) {
                 return replacement;
             }
  
             if (!el) {
                 return replacement;
             }
  
             el.appendChild(replacement);
  
             return el;
         },
  
         replaceMatch: function(match, startPortion, innerPortions, endPortion) {
  
             var matchStartNode = startPortion.node;
             var matchEndNode = endPortion.node;
  
             var precedingTextNode;
             var followingTextNode;
  
             if (matchStartNode === matchEndNode) {
  
                 var node = matchStartNode;
  
                 if (startPortion.indexInNode > 0) {
                     // Add `before` text node (before the match)
                     precedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode));
                     node.parentNode.insertBefore(precedingTextNode, node);
                 }
  
                 // Create the replacement node:
                 var newNode = this.getPortionReplacementNode(
                     endPortion,
                     match
                 );
  
                 node.parentNode.insertBefore(newNode, node);
  
                 if (endPortion.endIndexInNode < node.length) { // ?????
                     // Add `after` text node (after the match)
                     followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode));
                     node.parentNode.insertBefore(followingTextNode, node);
                 }
  
                 node.parentNode.removeChild(node);
  
                 this.reverts.push(function() {
                     if (precedingTextNode === newNode.previousSibling) {
                         precedingTextNode.parentNode.removeChild(precedingTextNode);
                     }
                     if (followingTextNode === newNode.nextSibling) {
                         followingTextNode.parentNode.removeChild(followingTextNode);
                     }
                     newNode.parentNode.replaceChild(node, newNode);
                 });
  
                 return newNode;
  
             } else {
                 // Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
  
  
                 precedingTextNode = doc.createTextNode(
                     matchStartNode.data.substring(0, startPortion.indexInNode)
                 );
  
                 followingTextNode = doc.createTextNode(
                     matchEndNode.data.substring(endPortion.endIndexInNode)
                 );
  
                 var firstNode = this.getPortionReplacementNode(
                     startPortion,
                     match
                 );
  
                 var innerNodes = [];
  
                 for (var i = 0, l = innerPortions.length; i < l; ++i) {
                     var portion = innerPortions[i];
                     var innerNode = this.getPortionReplacementNode(
                         portion,
                         match
                     );
                     portion.node.parentNode.replaceChild(innerNode, portion.node);
                     this.reverts.push((function(portion, innerNode) {
                         return function() {
                             innerNode.parentNode.replaceChild(portion.node, innerNode);
                         };
                     }(portion, innerNode)));
                     innerNodes.push(innerNode);
                 }
  
                 var lastNode = this.getPortionReplacementNode(
                     endPortion,
                     match
                 );
  
                 matchStartNode.parentNode.insertBefore(precedingTextNode, matchStartNode);
                 matchStartNode.parentNode.insertBefore(firstNode, matchStartNode);
                 matchStartNode.parentNode.removeChild(matchStartNode);
  
                 matchEndNode.parentNode.insertBefore(lastNode, matchEndNode);
                 matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode);
                 matchEndNode.parentNode.removeChild(matchEndNode);
  
                 this.reverts.push(function() {
                     precedingTextNode.parentNode.removeChild(precedingTextNode);
                     firstNode.parentNode.replaceChild(matchStartNode, firstNode);
                     followingTextNode.parentNode.removeChild(followingTextNode);
                     lastNode.parentNode.replaceChild(matchEndNode, lastNode);
                 });
  
                 return lastNode;
             }
         }
  
     };
  
     return exposed;
  
  }));