Elias Elias - 2 months ago 7
jQuery Question

Wrap a javascript generated html structure in a span with multiple instances of the end point

I've been working on a syntax highlighter for a slightly modified version of Velocity. In short how can I wrap all the content starting at

#inline()
and ending with the corresponding
#end
under the condition that there can be 0-infinite if statements that will always have an
#end
. Let me know if you have any questions.

Example can be found on this fiddle. See the below for more detailed information.

Sample HTML

The fiddle shows the post javascript modified version of the below html.

<pre><code>
$javascript.push("/path/to/file")
#inline()
$('.lazy.bg2').attr({'data-src':'/img_path2.jpg'}).css('background','red');
#if($myVar == "hi")
$('.someClass).hide()
#elseif($myVar == "there")
$('.lazy.bg1').attr({'data-src':'/img_path.jpg'})
#else
$('.lazy.bg3').attr({'data-src':'/img_path3.jpg'})
#end
$('.lazy.bg2 a').attr({'data-href':'/random-path2.htm'})
$('.lazy.bg1 a').attr({'data-href':'/random-path.htm'})
#end

#if($test.method == "myVal")
#set($foo = "swag")
#elseif($foo == "bar")
#set($foo = "ballin")
#elseif($myObject.randomMethod().contains("myVal"))
#set($foo = "weeee")
#else
#set($foo = "sad days")
#end
#set($testVar = "Test value")
#parse("/path/to/file")</code></pre>


The Problem

Since there are multiple instances of
#end
I'm not sure how to get it to match the end to the
#inline()
statement. The main problem being is that there could be an infinite number of if statements, but the
#inline()
will always have a corresponding end statement. So I'm guessing the best approach would be to match it based on it being the same white-space level. However I'm not sure if there is a better solution. I found the original javascript in Gone Coding's post here. However I've slightly modified it to better match my implementation. Note I'm adding the velocity class to the
<pre>
in an earlier jQuery statement.
The end result should only apply the
<span>
to the jQuery inside the
#inline()
.

Javascript

$('pre.velocity code').each(function () {
var open = false;
var result = $();
$(this).contents().each(function () {
var $this = $(this);
if ($this.text() == "#inline()" || $this.text() == "#end") {
if (open) {
result.wrapAll('<span class="velocity-inline-inner"></span>');
open = false;
} else {
result = $();
open = true;
}
} else {
result = result.add($this)
}
});
if (open) {
result.wrapAll('<span class="velocity-inline-inner"></span>');
}
});


Updated Javascript

I have the following that can count the white space and selects the
#end
that matches white space level of the
#inline()
, however I am having the issue of converting just that substring to html within the if statement.

$('pre.velocity code').each(function(){
var str = $(this).text();
str = str.substring(str.indexOf("#inline()") + 10);
textArray = str.split("\n");
getInlineEnd(str,textArray);
});

function getInlineEnd(str,textArray) {
for(var i = 0; i <= textArray.length; i++) {
if(textArray[i].length === 4 && textArray[i] === "#end") {
//convert textArray[i] to a html node and then wrap with a <span>
break;
}
}
}


End Goal HTML

The end result should look something like the below. I'm adding a span around the
#inline()
and
#end
already.

#inline()
<span class="velocity-inline-inner">
$('.lazy.bg2').attr({'data-src':'/img_path2.jpg'}).css('background','red');
#if($myVar == "hi")
$('.someClass).hide()
#elseif($myVar == "there")
$('.lazy.bg1').attr({'data-src':'/img_path.jpg'})
#else
$('.lazy.bg3').attr({'data-src':'/img_path3.jpg'})
#end
$('.lazy.bg2 a').attr({'data-href':'/random-path2.htm'})
$('.lazy.bg1 a').attr({'data-href':'/random-path.htm'})
</span>
#end


I believe once I get the above code working correctly then it should syntax highlight appropriately. The end goal though is to get all of the jQuery method values highlighting. I am going to be handling the selector under a different regex.

Answer

I ended up doing the below js and html to wrap the #inline() and the corresponding #end based on whitespace. This was the html rendered by the fiddle.

    // Loop thorugh each of the velocity and see if there is an inline to syntax highlight.
    $('pre.velocity code').each(function(){
        var html = $(this).html();
        var str = html.toString(html); // convert html object to a string
        str = str.substring(str.indexOf("#inline()") + 17); // split the string so only take half of it.
        var textArray = str.split("\n"); // split the array on a new line
        var newHtml = getInlineEnd(textArray);
        regExVelocity($(this),str,newHtml); // replace the string
    });

    /**
     * Loops through the inline and finds the #end based on white-space
     * @param textArray
     * @type Array
     * @returns {string|*}
     */
    function getInlineEnd(textArray) {
        // loop through each line of the string.
        for(var i = 0; i <= textArray.length; i++) {
            if(i == 0) {
                // first line in #inline()
                textArray[i] = "<span class=\"velocity-inline-inner\">"+textArray[i]
            }
            if(undefined !== textArray[i] && textArray[i].length === 38 && textArray[i] === "<span class=\"velocity-end\">#end</span>") {
                // Found the end of the #inline() based on white-space. Update the text.
                textArray[i] = textArray[i].replace(textArray[i],'</span><span class="velocity-end">#end</span>');
                // break out of the for loop since we got the value
                break;
            }
        }
        // return the string and reformat it.
        return textArray.join('\n');
    }

    /**
     * Runs regex for velocity syntax highlighting uses .html() if you want to use the text then use regExText
     * @param selector
     * @param regex
     * @param output
     */
    function regExVelocity(selector, regex, output) {
        selector.html(function(i, html) {
            return( html.replace(regex, output));
        });
    }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre class="velocity"><code>
<span class="velocity-object">$javascript</span>.push(<span class="velocity-string">"/path/to/file"</span>)
<span class="velocity-inline">#inline()</span>
    $('.lazy.bg2').attr({'data-src':'/img_path2.jpg'}).css('background','red');
    <span class="velocity-if">#if(</span><span class="velocity-if-inner">$myVar == "hi"</span><span class="velocity-if">)</span>
    #elseif($myVar == "there")
          $('.lazy.bg1').attr({'data-src':'/img_path.jpg'})
    #else
          $('.lazy.bg3').attr({'data-src':'/img_path3.jpg'})
    <span class="velocity-end">#end</span>
    $('.lazy.bg1 a').attr({'data-href':'/random-path.htm'})
<span class="velocity-end">#end</span>
<span class="velocity-if">#if(</span><span class="velocity-if-inner"><span class="velocity-object">$test</span>.method == "myVal"</span><span class="velocity-if">)</span>
  <span class="velocity-set">#set(</span><span class="velocity-inner"><span class="velocity-variable">$foo </span><span class="equalSign">=</span> "swag"</span><span class="velocity-set">)</span>
#elseif($foo == "bar")
  <span class="velocity-set">#set(</span><span class="velocity-inner"><span class="velocity-variable">$foo </span><span class="equalSign">=</span> "ballin"</span><span class="velocity-set">)</span>
#elseif(<span class="velocity-object">$myObject</span>.randomMethod().contains(<span class="velocity-string">"myVal"</span>))
  <span class="velocity-set">#set(</span><span class="velocity-inner"><span class="velocity-variable">$foo </span><span class="equalSign">=</span> "weeee"</span><span class="velocity-set">)</span>
#else
  <span class="velocity-set">#set(</span><span class="velocity-inner"><span class="velocity-variable">$foo </span><span class="equalSign">=</span> "sad days"</span><span class="velocity-set">)</span>
<span class="velocity-end">#end</span>
<span class="velocity-set">#set(</span><span class="velocity-inner"><span class="velocity-variable">$testVar </span><span class="equalSign">=</span> "Test value"</span><span class="velocity-set">)</span>
<span class="velocity-parse">#parse(</span><span class="velocity-parse-path">"/path/to/file"</span><span class="velocity-parse">)</span></code></pre>