Skip to content

Instantly share code, notes, and snippets.

@christoomey
Created November 15, 2012 00:31
Show Gist options
  • Select an option

  • Save christoomey/4075843 to your computer and use it in GitHub Desktop.

Select an option

Save christoomey/4075843 to your computer and use it in GitHub Desktop.
Javascript utility to wrap instances of a search string within a target string
window.CJT or= {}
class CJT.SpanWrapper
STRING_NOT_FOUND: -1
constructor: (options={}) ->
@tagname = options.tagname ? 'span'
@classname = options.classname ? ''
wrap: (target, searchString) =>
return target if searchString is ''
normalizedSearchString = @_normalize searchString
matchSegments = @_splitMatchSegments(target, normalizedSearchString)
if matchSegments is @STRING_NOT_FOUND
target
else
@_wrapMatches(matchSegments)
_splitMatchSegments: (target, searchString) =>
rangeFromSearchStringLengthToZero = [(searchString.length - 1)..0]
for index in rangeFromSearchStringLengthToZero
[searchTestSegment, leftoverSearch] = @_splitAtIndex(searchString, index)
if @_contains(target, searchTestSegment)
splitTarget = @_splitAround(target, searchTestSegment)
if leftoverSearch is ''
unmatchedEnd = start: splitTarget.end, match: ""
return [splitTarget].concat(unmatchedEnd)
else
splitEnd = @_splitMatchSegments(splitTarget.end, leftoverSearch)
if splitEnd is @STRING_NOT_FOUND
continue
else
return [splitTarget].concat(splitEnd)
@STRING_NOT_FOUND
_wrapMatches: (matchSegments) =>
wrappedTarget = ''
for segment in matchSegments
wrappedMatch = @_wrapped(segment.match)
wrappedTarget += (segment.start + wrappedMatch)
wrappedTarget
_splitAtIndex: (string, zeroBasedIndex) =>
start = string[0..zeroBasedIndex]
end = string[(zeroBasedIndex + 1)..(string.length - 1)]
[start, end]
_contains: (target, substring) =>
target.toLowerCase().indexOf(substring) >= 0
_normalize: (string) => string.toLowerCase().replace(/\s+/g, '')
_wrapped: (core) =>
return '' if core is ''
(@_openTag() + core + @_closeTag())
_openTag: =>
if @classname
"<#{@tagname} class='#{@classname}'>"
else
"<#{@tagname}>"
_closeTag: => "</#{@tagname}>"
_splitAround: (target, substring) =>
downcasedTarget = target.toLowerCase()
index = downcasedTarget.indexOf substring
start = target.substring(0, index)
match = target.substring(index, index + substring.length)
end = target.substring(index + substring.length, target.length)
start: start, match: match, end: end
#= require spanWrapper
describe 'SpanWrapper', ->
wrapper = new CJT.SpanWrapper
target = 'a random target'
describe '#wrap', ->
it 'returns the target with the searchString wrapped in a span', ->
searchString = 'random'
wrapped = wrapper.wrap(target, searchString)
expect(wrapped).toEqual('a <span>random</span> target')
it 'returns the target unchanged if the searchString is empty', ->
searchString = ''
wrapped = wrapper.wrap(target, searchString)
expect(wrapped).toEqual(target)
it 'returns the target if the searchString is not in the target', ->
searchString = 'XZX'
wrapped = wrapper.wrap(target, searchString)
expect(wrapped).toEqual(target)
it 'uses the provided tagname to wrap the searchString', ->
divWrapper = new App.Util.SpanWrapper(tagname: 'div')
searchString = 'random'
wrapped = divWrapper.wrap(target, searchString)
expect(wrapped).toEqual('a <div>random</div> target')
it 'adds the class to the tag if provided', ->
wrapperWithClass = new App.Util.SpanWrapper(classname: 'highlighted')
searchString = 'random'
wrapped = wrapperWithClass.wrap(target, searchString)
expect(wrapped).toEqual("a <span class='highlighted'>random</span> target")
it 'ignores case in both target and searchString', ->
caseSensitiveTarget = 'AsDf'
caseSensitiveSearchString = 'aSdF'
wrapped = wrapper.wrap(caseSensitiveTarget, caseSensitiveSearchString)
expect(wrapped).toEqual("<span>#{caseSensitiveTarget}</span>")
it 'ignores whitespace in the search string', ->
searchString = 'dom t'
wrapped = wrapper.wrap(target, searchString)
expect(wrapped).toEqual('a ran<span>dom</span> <span>t</span>arget')
it 'returns the target with each piece of the searchString wrapped', ->
searchString = 'rantar'
wrapped = wrapper.wrap(target, searchString)
expect(wrapped).toEqual('a <span>ran</span>dom <span>tar</span>get')
it 'wraps each match segment of a discontiguous match individually', ->
discontiguousTarget = 'Windows 7 License Decision'
discontiguousSearchString = 'iss'
wrapped = wrapper.wrap(discontiguousTarget, discontiguousSearchString)
expected = 'W<span>i</span>ndow<span>s</span> 7 Licen<span>s</span>e Decision'
expect(wrapped).toEqual(expected)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment