0

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.

Community
  • 1
  • 1
Elias Ranz
  • 401
  • 1
  • 6
  • 21

2 Answers2

0

I had a quick look at your code, but it's a bit of a beast. So I'll give a more general overview. First you need to be able to parse (to some small degree) some of the parts of your input (for this, #inline, #if, and #end). Then you need to count the number of openings that would close with #end. Then as each #end comes along, you check the latest opening off the list. When you get to the #end that would close the opening #inline, that's where you close off that </span>.

Note: I haven't tested the code. I don't know exactly how the code originally worked and so on. I'm just using it as pseudo-code to show what kind of things you'll need to do.

$('pre.velocity code').each(function () {
    var open = [];
    var result = $();
    $(this).contents().each(function () {
        var $this = $(this);
        if ($this.text() == "#inline()" || $this.text().substr("#if(") === 0) {
            open.push($this);
        }
        else if ($this.text() == "#end") {
            var toClose = open.pop();
            if (toClose.text() === "#inline()") {
                // wrap from after #inline() to before #end
            }
        }
    });
});
Whothehellisthat
  • 2,072
  • 1
  • 14
  • 14
  • Ends up saying that there is a TypeError where it can't read text property of undefined and points to `if (toClose.text() === "#inline()") {`. So it sounds like the pop() isn't returning an object with a text node. – Elias Ranz Sep 18 '16 at 05:02
  • Oh right. Best do some debugging then? As I say, I don't fully understand your code, so the code probably won't work just copy/pasted. – Whothehellisthat Sep 20 '16 at 18:13
0

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>
Elias Ranz
  • 401
  • 1
  • 6
  • 21