8
\$\begingroup\$

Background

The basics of this project have already been discussed here.

I've improved the following things:

Sᴀᴍ Onᴇᴌᴀ mentioned that I shouldn't use multiple elements with the same id. I corrected that and use classes instead. I also removed units from style.css when the value is 0. Furthermore I removed !important.

Sᴀᴍ Onᴇᴌᴀ also pointed out, I did not follow always follow the common style-guide. I tried to improve it for this follow-up question.


Mast mentioned that it is not possible to have html-tags in for example comments, without destroying the formatting.

I've solved this problem by replacing & and <:

text = text.replace(new RegExp("&", "g"), "&amp;");
text = text.replace(new RegExp("<", "g"), "&lt;");

FeRD noted, that inputs like System.out.println("Hi \"friend\"."); cause problems because of the escaped quotes. For this problem i used the solution provided here.

Another problem was to highlight private Account my_import_export;. This problem also was solved by changing the regex a bit.


Charlie Harding mentioned in the comments that something like serif (x), if (x) y(); or if(x) y() is not possible. This problem was also solved.


Olivier Grégoire noted, that "\"" and "\\\"" are not highlighted correctly. I solved this problem also.


Roland Illig suggested to properly parse the java-code. I did not implement this suggestion, because I will first try how far I can come by just using regex.


The code

//Add listener to highlight-button
document.getElementById('highlightButton').addEventListener('click', highlight);

//Add possibility to use tabs in inputArea
var area = document.getElementById('Input');
area.onkeydown = function(e) {
    if(e.keyCode == 9){
        e.preventDefault();
        this.value = this.value.substring(0,this.selectionStart) + '\t' + this.value.substring(this.selectionEnd);
    }
}

//===============================
//Highlighting-functionality
//===============================

var keywordsColor = '#0033cc';
var controlKeywordsColor = '#009933';
var typesKeywordsColor = '#3399ff';
var stringColor = '#ff3300';
var importColor = '#0033cc';
var commentColor = 'gray';

var text;

var keywords = ['abstract', 'assert', 'const', 'extends', 'false', 'final',
'implements', 'import', 'instanceof', 'interface', 'native', 'new', 'null', 'package',
'private', 'protected', 'public', 'return', 'static', 'strictfp', 'super', 'synchronized',
'System', 'this', 'throw', 'throws', 'transient', 'true', 'volatile'];

var controlKeywords = ['break', 'case', 'catch', 'continue', 'default', 'do', 'else',
'finally', 'for', 'goto', 'if', 'switch', 'try', 'while'];

var typesKeywords = ['boolean', 'byte', 'char', 'double', 'enum', 'float', 'int',
'long', 'short', 'String', 'void'];
var otherKeywords = [];

function highlight() {
    text = document.getElementById('Input').value;
    text = text.replace(new RegExp('&', 'g'), '&amp;');
    text = text.replace(new RegExp('<', 'g'), '&lt;');
    findOtherKeywords();
    highlightKeywords();
    highlightStrings();
    highlightImports();
    highlightSingleLineComments();
    highlightMultiLineComments();

    var arr = text.split("\n");
    for(var i = 0; i < arr.length; i++) {
        arr[i] = '<code>' + arr[i] + '</code>';
    }

    text = arr.join('\n');
    addStyles();

    var check = document.getElementById('checkbox2');
    if(check.checked) {
        text = '<div style = \'background: LightGray; font-family: monospace;\'>' + text + '</div>';
    }
    document.getElementById('Output').value = text;
    document.getElementById('outputArea').innerHTML = text;

}

function findOtherKeywords() {
    var arr = text.split('\n');
    for(var i = 0; i < arr.length; i++) {
        if(arr[i].startsWith('import')) {
            arr[i] = arr[i].substring(7, arr[i].indexOf(';'));
            var array = arr[i].split('\.');
            for(var j = 0; j < array.length; j++) {
                typesKeywords.push(array[j]);
            }
        }
    }
}

function highlightKeywords() {
    var i;
    for (i = 0; i < typesKeywords.length; i++) {
        var x = new RegExp(`\\b${typesKeywords[i]}\\b`, 'g');
        var y = '<span class=\'typesKeywordsClass\'>' + typesKeywords[i] + '</span>';
        text = text.replace(x, y);
    }

    for (i = 0; i < keywords.length; i++) {
        var x = new RegExp(`\\b${keywords[i]}\\b`, 'g');
        var y = '<span class=\'keywordsClass\'>' + keywords[i] + '</span>';
        text = text.replace(x, y);
    }

    for (i = 0; i < controlKeywords.length; i++) {
        var x = new RegExp(`\\b${controlKeywords[i]}\\b`, 'g');
        var y = '<span class=\'controlKeywordsClass\'>' + controlKeywords[i] + '</span>';
        text = text.replace(x, y);
    }

    var x = new RegExp(`\\b(class) `, 'g');
    var y = '<span class=\'keywordsClass\'>' + 'class ' + '</span>';
    text = text.replace(x, y);

}

function highlightStrings() {
    text = text.replace(/"([^"\\]*(\\.[^"\\]*)*)"/,
         '<span class="str" style="color:' + stringColor + '; font-weight:bold;">' 
         + '"$1"' + '</span>');
}

function highlightImports() {
    text = text.replace(/^import(.*?);/g,
         '<span class="str" style="color:' + importColor + '; font-weight:bold;">' 
         + 'import$1;' + '</span>');
}

function highlightSingleLineComments() {
    text = text.replace(/\/\/(.*)/g,
         '<span class="comment" style="color:' + commentColor + '; font-weight:bold;">' 
         + '//$1' + '</span>');
}

function highlightMultiLineComments() {
    text = text.replace(/[ ]*\/\*([\s\S]*?)\*\//g, (...match) => {
        let groups = match.pop();
        var str = groups + '';
        var arr = str.split('\n');
        var i = 0;
        var j = 0;
        var test = false;

        for(i = 0; i < arr.length; i++) {
            if(arr[i].includes('*/')) {
                var arr2 = arr[i].split("*/");
                arr2[0] = '<span class="comment" style="color:' + commentColor + '; font-weight:bold;">' 
                    + arr2[0] + '</span>';
                arr[i] = arr2.join('<span class="comment" style="color:' + commentColor + '; font-weight:bold;">' 
                    + '*/' + '</span>')
                break;
            }

            if(arr[i].includes('/*') || test) {
                if(!test) {
                    j = i;
                }
                arr[i] = '<span class="comment" style="color:' + commentColor + '; font-weight:bold;">' 
                    + arr[i] + '</span>';
                test = true;

            } 

        }
        var result = arr.slice(j, i + 1).join('\n');
        return result;
    });

}

function addStyles() {
    var style = '<!-- Style begins here (copy to head) -->\n'
        + '<style> .keywordsClass {color:' + keywordsColor + ';font-weight:bold;}</style>'
        + '<style> .controlKeywordsClass {color:' + controlKeywordsColor + ';font-weight:bold;}</style>'
        + '<style> .typesKeywordsClass {color:' + typesKeywordsColor + ';font-weight:bold;}</style>'
        + '<style>code .comment span {color:' + commentColor + ';}</style>'
        + '<style>code .str span {color:' + stringColor + ';}</style>';

    if(document.getElementById('checkbox1').checked) {
        style = style 
            + '<style> pre{counter-reset: line;}</style>'
            + '<style> code{counter-increment: line;}</style>'
            + '<style> code::before {content: counter(line); display: inline-block; width: 2.5em; border-right: 2px solid gray; padding: 0.2em; margin-right: 0.5em; color: gray;}</style>';
    }

    style = style 
        + '\n<!-- Style ends here -->\n';
 
    text = style
        + '\n<!-- Code begins here -->\n'
        + '\n<pre>\n' 
        + text
        + '\n</pre>\n<!-- Code ends here -->\n';
}
body {
    tab-size: 4;
    -moz-tab-size: 4;
    margin: 0;
    margin-top: 0;
    /* Background pattern from Toptal Subtle Patterns */
    background: url('../pictures/extra_clean_paper.png');
    background-attachment: fixed;
    height: 100%;
    width: 100%;
    position: absolute;
    overflow: -moz-scrollbars-vertical; 
    overflow-y: scroll;
}

.inner {
    tab-size: 4;
    -moz-tab-size: 4;
    margin: 0;
    margin-top: 0;
    background: white;
    height: 100%;
    width: 80%;
    display: table;
    table-layout: fixed;
    margin: 0 auto;
    padding: 0;
    position: relative;
    font-family: sans-serif;
}

/* Navigation bar style */
header {
    display: table-row;
    background: #252628;
    height: 40px;
    float: right;
    width: 100%;
    position: relative;
    display: block;
}

header ul { 
    background: #252628;
    list-style: none;
    padding: 0;
    margin: 0;
    position: fixed;
    width: 80%;
    top: 0;
    z-index:9999;

}

header li {
    float: left;
    border-right: 2px solid LightGray;
    height: 40px;
}

header a {
    color: black;
    display: inline-block;
    font-size: large;
    text-decoration: none;
    padding: 4px;
    padding-left: 10px;
    padding-right: 10px;
    width: auto;
    color: LightGray;
    line-height: 40px;
    vertical-align: middle;

}

header li:hover {
    background: #688B57;

}

header  li ul {
    display: none;

}

header li:hover > ul {
    display:block;
    list-style:none;
    height: 40px;
    margin-top: 40px;
    background: #252628;
    width: auto;
    
}

header ul li ul li{
  height: 40px;
  display: block;
  float:none;
  text-align: left;
  border-top: 2px solid LightGray;
  border-right: none;
}

.icon {
    padding-right: 4px;
    line-height: 40px;
    vertical-align: middle;
}

/* Main */
main {
    display: table-row;
    width: 80%;
    height: 100%;

}

/* Footer */
footer {
    display: table-row;
    background: #252628;
    min-height: 60px;
    height: auto;
    width: 100%;
    float: right;
    bottom: 0;
    position: relative;
}

footer ul {
    text-align: center;
    padding: 0;
    overflow: hidden;
}

footer ul li {
    list-style: none;
    display: inline;
    width: auto;
    height: 60px;
    padding: 40px;
    line-height: 60px;
    vertical-align: middle;
    justify-content:center;
}

footer ul li a  {
    text-decoration: none;
    color: LightGray;
}

/* Other */
#code {
    background: LightGray;
    font-family: monospace;
}

.column {
  float: left;
  width: 50%;
}

.caption {
    text-decoration: none;
}

.icons {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    text-align: right;

}

.right {
    float: right; 
    width: 40px;

}

.rightLink {
    width: 40px;
    padding-left: 0;
    padding-right: 0;
    text-align: center;
}

h2 {
    font-family: "Courier";
    font-style: italic;
    text-transform: uppercase;
    color: #252628;
}

.list > li {
  margin: 5px 0;
}

.input {
    width: 80%;
    border: 2px solid #252628;
    padding: 20px;
    background: LightGray;
}

.formText {
    border: 1px solid #252628;
    box-shadow: none;
    margin-bottom: 30px;
    resize: none; 
    width: 100%; 
    position: relative;
    font-size: 12px;
    height: 16px;
    padding-top: 2px;
    padding-bottom: 2px;
}

.formText:focus {
    border:1px solid green;
}

figure {
    text-align: center;
    float: right;
    display: block;
    position: relative;
}

figure img {
    display: block;
    margin-bottom: 20px;
}

figcaption {
    position: absolute;
    bottom: 0;
}
<!DOCTYPE html>
<html lang='en'>
    <!-- Head -->
    <head>
        <script>
            window.onerror=function(msg, url, linenumber) {
                alert('Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber);
                return true;
            }
        </script>
        <meta charset='utf-8'>
        <title>Highlighting</title>
        <link rel='stylesheet' type='text/css' href='../css/style.css'>
    </head>

    <body>
        <div class='inner'>
            <!-- Navigation bar -->
            <header>
                <div class='nav'>
                    <ul>
                        <li><a href='../index.html'><img class='icon' src='../pictures/iconmonstr-home-7-24.png' alt=''/>Home</a></li>
                        <li><a href='tools.html'><img class='icon' src='../pictures/iconmonstr-wrench-2-24.png' alt=''/>Tools</a>
                            <ul>
                                <li><a href='highlighting.html'>Highlighting</a></li>
                            </ul>
                        </li>
                        <li><a href='about.html'><img class='icon' src='../pictures/iconmonstr-user-6-24.png' alt=''/>About</a></li>
                        <li><a href='contact.html'><img class='icon' src='../pictures/iconmonstr-email-2-24.png' alt=''/>Contact</a></li>
                      
                    </ul> 

                </div>
            </header>

            <main>
                <div style='margin:30px; margin-top: 50px'>
                    <h2>HTML syntax-highlighting for Java</h2>

                    <!-- Left column -->
                    <div class='column'>

                        <!-- Input Area -->
                        <h4>Input:</h4>
                        <div>
                            <textarea id='Input' rows='8' style='resize: none; background: LightGray; position: relative; width: 80%;'></textarea>
                        </div>
                        <br>
                        <form>
                            <input type='checkbox' id='checkbox1' name='checkbox1' value='numbers'>
                            <label for='checkbox1'>Include line numbers</label><br>
                            <input type='checkbox' id='checkbox2' name='checkbox2' value='background'>
                            <label for='checkbox2'>Include background-color</label>
                        </form>  
                        <br><br>
                        <button type='button' id='highlightButton'>Highlight</button> 

                        <!-- Output Area -->
                        <h4>Output:</h4>
                        <div>
                            <textarea id='Output' rows='8' style='resize: none; background: LightGray; position: relative; width: 80%;'></textarea>
                        </div>  

                    </div>

                    <!-- Right Column -->
                    <div class='column'>
                        <h4>Preview</h4>
                        <div id='outputArea' style='overflow-y:auto; overflow-x:auto; height: 690px'></div>
                    </div>
                </div>
            </main>

            <footer>
                <ul>
                    <li><a href='../index.html'>Home</a></li>
                    <li><a href='about.html'>About</a></li>
                    <li><a href='license.html'>License</a></li>
                    <li><a href='impressum.html'>Impressum</a></li>
                    <li><a href='datenschutz.html'>Datenschutz</a></li>
                </ul> 
            </footer>
        </div>

        <script src='../javascript/highlightSyntax.js'></script>
    </body>
</html>

I've checked the syntactic validity of the javascript-code with this tool and the html/css-code with this one.

Example

If you want to test the code, you can use the following snippet:

import java.time.LocalDate; 

public class Person {
    //Local variable for dateOfBirth
    private LocalDate dateOfBirth;    

    public Person(int year, int month, int day) {
        //See API also: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
        dateOfBirth = LocalDate.of(year, month, day);
        //Keywords (e.g. int) are not highlighted in comments and strings
        System.out.println("Hello (int)");
    }

    /*
     * Getter "DateOfBirth"
     */
    public LocalDate getDateOfBirth() {
        return dateOfBirth;
    }
}

Question

All suggestions are welcome.

\$\endgroup\$
4
\$\begingroup\$

I'm glad to see you incorporated much of the feedback from the answers to your previous post. I see you added the colors into CSS, but dynamically. I believe those styles for the colors could exist with the static CSS. Actually the whole addStyles() function could likely be eliminated using conditional classes (e.g. if checkbox1 is checked, then add an extra class name to the element with id outputArea and add those styles when the class is applied - e.g.

#outputArea.showLineNums pre{
    counter-reset: line;
}
#outputArea.showLineNums code{
    counter-increment: line;
}
#outputArea.showLineNums code::before {
    content: counter(line); 
    display: inline-block; 
    width: 2.5em; 
    border-right: 2px solid gray; 
    padding: 0.2em;
    margin-right: 0.5em; 
    color: gray;
}

I see there is an arrow function in highlightMultiLineComments() as well as the let keyword. Those are features of (A.K.A. ES-6) and there are some others that could be used - e.g. for...of loops could really simplify the syntax of for loops (though performance may suffer because they use iterators). Many modern JS programmers avoid using the var keyword unless a global variable is needed (which many programmers hold is bad practice anyway). A common recommendation is to use const as a default and then use let when you determine a value can be re-assigned (e.g. loop variables). For more information about ES-6 see the MDN documentation (including browser support) as well as http://es6-features.org/

The last two lines of that arrow function assigns and returns a single-use variable (i.e. result). Some may argue that the extra variable allocates extra memory though for a small case like this it is likely not going to make much of a difference. This might be something a linter would catch. If you haven't already done so, you may want to start using a linter - e.g. esLint, jsLint.


The first line adds an event listener to the element with id highlightButton using addEventListener(). Then after that the onkeydown property is set on the element with id Input. The keydown event listener could be set using addEventListener(), which would allow multiple listeners to be added if necessary.


Values in variables like keywordsColor don't appear to change so those could be put in constants - and a common convention is to use ALL_CAPS so anyone reading the code can tell the value doesn't change, and declare them at the top of your code (or in a separate file/module):

const KEYWORDS_COLOR = '#0033cc';
const CONTROL_KEYWORDS_COLOR = '#009933';

const KEYWORDS = Object.freeze(['abstract', 'assert', ...]);

Then if you need to modify those values they can be found easily. If you want to ensure that a constant can't be modified (e.g. an array can't be modified via .push()) the value can be wrapped in [Object.freeze()].


The name of the checkboxes could be improved - e.g. instead of checkbox1 and checkbox2, names like includeLineNums and includeBGcolor would be more descriptive and not much longer.


The CSS has these padding rules under header a:

padding: 4px;
padding-left: 10px;
padding-right: 10px;

Those can be combined into a single rule:

padding: 4px 10px

The same applies to the ruleset for .formText:

padding-top: 2px;
padding-bottom: 2px;

Can be combined to:

padding: 2px 0;
| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ Nice answer. This is the first time I've seen ES6 be called ES-6. \$\endgroup\$ – Peilonrayz Jul 31 at 17:19
4
\$\begingroup\$

Mast mentioned that it is not possible to have html-tags in for example comments, without destroying the formatting.

I've solved this problem by replacing & and <:

text = text.replace(new RegExp("&", "g"), "&amp;");
text = text.replace(new RegExp("<", "g"), "&lt;");

You can safely escape everything by using Option. I would prefer this as everything gets escaped correctly rather than leaving it to chance.

text = new Option(text).innerHTML;
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.