( function ( root , factory ) { if ( typeof window !== 'undefined' ) { 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 ( ) { 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 ) ) ; } var instance = findAndReplaceDOMText ( node , { find : regex , wrap : isReplacementFunction ? null : replacement , replace : isReplacementFunction ? replacement : '$' + ( captureGroup || '&' ) , prepMatch : function ( m , mi ) { 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 ; } param { Node } node param { RegExp } options . find param { String | Element } options . wrap param { String } options . wrapClass param { String | Function } options . replace param { Function } options . filterElements function findAndReplaceDOMText ( node , options ) { return new Finder ( node , options ) ; } exposed . NON_PROSE_ELEMENTS = { br : 1 , hr : 1 , script : 1 , style : 1 , img : 1 , video : 1 , audio : 1 , canvas : 1 , svg : 1 , map : 1 , object : 1 , input : 1 , textarea : 1 , select : 1 , option : 1 , optgroup : 1 , button : 1 } ; exposed . NON_CONTIGUOUS_PROSE_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 , br : 1 , li : 1 , summary : 1 , dt : 1 , details : 1 , rp : 1 , rt : 1 , rtc : 1 , script : 1 , style : 1 , img : 1 , video : 1 , audio : 1 , canvas : 1 , svg : 1 , map : 1 , object : 1 , input : 1 , textarea : 1 , select : 1 , option : 1 , optgroup : 1 , button : 1 , 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 ( ) ) ; } ; 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 ; 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' ) { 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' ) { 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 , matchIndex = 0 , portionIndex = 0 , doAvoidNode , nodeStack = [ node ] ; out : while ( true ) { if ( curNode . nodeType === Node . TEXT_NODE ) { if ( ! endPortion && curNode . length + atIndex >= match . endIndex ) { endPortion = { node : curNode , index : portionIndex ++ , text : curNode . data . substring ( match . startIndex - atIndex , match . endIndex - atIndex ) , indexInMatch : atIndex === 0 ? 0 : atIndex - match . startIndex , indexInNode : match . startIndex - atIndex , endIndexInNode : match . endIndex - atIndex , isEnd : true } ; } else if ( startPortion ) { innerPortions . push ( { node : curNode , index : portionIndex ++ , text : curNode . data , indexInMatch : atIndex - match . startIndex , indexInNode : 0 } ) ; } if ( ! startPortion && curNode . length + atIndex > match . startIndex ) { 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 ) ; atIndex -= ( endPortion . node . data . length - endPortion . endIndexInNode ) ; startPortion = null ; endPortion = null ; innerPortions = [ ] ; match = matches . shift ( ) ; portionIndex = 0 ; matchIndex ++ ; if ( ! match ) { break ; } } else if ( ! doAvoidNode && ( curNode . firstChild || curNode . nextSibling ) ) { if ( curNode . firstChild ) { nodeStack . push ( curNode ) ; curNode = curNode . firstChild ; } else { curNode = curNode . nextSibling ; } continue ; } while ( true ) { if ( curNode . nextSibling ) { curNode = curNode . nextSibling ; break ; } curNode = nodeStack . pop ( ) ; if ( curNode === node ) { break out ; } } } } , /**
      * Reverts ... TODO
      */ revert : function ( ) { 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 || '$&' ; if ( typeof replacement == 'function' ) { replacement = replacement ( portion , match ) ; if ( replacement && replacement . nodeType ) { return replacement ; } return doc . createTextNode ( String ( replacement ) ) ; } } , 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 ) { precedingTextNode = doc . createTextNode ( node . data . substring ( 0 , startPortion . indexInNode ) ) ; node . parentNode . insertBefore ( precedingTextNode , node ) ; } var newNode = this . getPortionReplacementNode ( endPortion , match ) ; node . parentNode . insertBefore ( newNode , node ) ; if ( endPortion . endIndexInNode < node . length ) { 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 { 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 ; } ) ) ;