0
votes

So I have a project that involves creating an html webapp that's similar to pastebin. And I have to add syntax highlighting but we're not allowed to used premade syntax highlighter or plugins. We have to do it from scratch. I know that I have to do it with javascript. Can anyone give me some insight on how I should create this syntax highlighting? I've only made some test for syntax highlighting from what I saw on yt, but mostly they are plugins

Here is the code for the test.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
code {
    display:block;
    white-space: pre-wrap;
    border: 1px solid #000;
    padding: 10px;
    line-height: 1.5em;
    font-family: "Lucida Console", Monaco, monospace;
}
.code-str { color : #090; }
.code-elem{ 
    color: #F90;
}
</style>
<script>
    function syntaxhighlights() {
        var ca = document.getElementsByTagName("code");
        for(var i = 0; i < ca.length; i++ ){
            var data = ca[i].innerHTML;
            data = data.replace (/"(.*?)"/g, '<span class="code-str">&quot;$1&quot;</span>');
            data = data.replace (/&lt;(.*?)&gt;/g, '<span class="code-elem">&lt;$1&gt;</span>');
            ca[i].innerHTML = data;
        }
}
window.addEventListener("load", syntaxhighlights);      
</script>
</head>
<body>
    <h2>Code Example:</h2>
    <code>&lt;h2 id="h21"&gt;Welcome Visitors&lt;/h2&gt;
&lt;p&gt;When in Rome, do as the Romans do.&lt;/p&gt;
    </code> 
</body>
1

1 Answers

0
votes

I have created a project involving syntax highlighting. The best way is to use regex excessively. You have to create classes for each token. You need a function to take the text and convert it into an html chunk. Using String.replace you can format the given text. Just create a render class. Mine was something like that(It has been so long since I wrote it. Hope this helps):

jsx-parse-engine.js

import ReactHtmlParser from 'react-html-parser'
import StyleSheet from '../styles/native/code.module.css'
import React from 'react'
import { insertArray, replaceString, updateArray } from './utility-functions'

// Add String.matchAll polyfill in case it is not implemented
;(() => {
    if (!String.prototype.matchAll) {
        String.prototype.matchAll = function (regex) {
            let match = regex.exec(this)
            const matches = []
            while (match !== null) {
                matches.push(match)
                match = regex.exec(this)
            }
            return matches
        }
    }
    console.log('matchAll polyfill has been configured.')
})()

const createJSXBuilder = (className) => (v) =>
    `<span class="${className}">${v}</span>`

const getMatchArray = (content, pattern, builder) => {
    let matchArray = []
    const results = Array.from(content.matchAll(pattern))
    for (const result of results) {
        const index = result.index
        const match = result[0]
        const length = match.length
        matchArray.push([index, length, match, builder])
    }
    return matchArray
}

const sortMatchArray = (matchArray) => {
    let sortedMatchedArray = []
    let g
    for (const matchItem of matchArray) {
        if (sortedMatchedArray.length === 0) {
            sortedMatchedArray.push(matchItem)
            continue
        }
        g = true
        for (let i = 0; i < sortedMatchedArray.length; i++) {
            if (matchItem[0] < sortedMatchedArray[i][0]) {
                sortedMatchedArray = insertArray(
                    sortedMatchedArray,
                    i,
                    matchItem
                )
                g = false
                break
            }
        }
        if (g) {
            sortedMatchedArray.push(matchItem)
        }
    }
    return sortedMatchedArray
}

const parse = (content, matchArray) => {
    let secretIndex = 0
    for (const match of matchArray) {
        const jsx = match[3](match[2])
        const beginIndex = match[0] + secretIndex
        content = replaceString(content, beginIndex, beginIndex + match[1], jsx)
        secretIndex += jsx.length - match[2].length
    }
    return content
}

export function parseSourceCodeToJSX(content) {
    let matchArray = []
    const newLine = /\n/g
    const tab = /\t/g
    const parserTokenMap = [
        [/@\w+\([\w, ]*\)\n/g, createJSXBuilder(StyleSheet.annotation)],
        [/(?<!(['"@]\w*\(?))[,():+\-*\[\];{}.!](?!\w*['"])/g, createJSXBuilder(StyleSheet.symbol)],
        [/\/\/.*/g, createJSXBuilder(StyleSheet.comment)],
        [/['"].*?['"]/g, createJSXBuilder(StyleSheet.string)],
        [/\b(?<!(['"]|\/\/ )\w*)\d+\.?\d*(?!\w*['"])\b/g, createJSXBuilder(StyleSheet.number)],
        [/(?<=\.)\w+(?=\()/g, createJSXBuilder(StyleSheet.method)],
        [/(?<=\.)\w+(?!\()\b/g, createJSXBuilder(StyleSheet.property)],
        [/\bthis(?=\.)/g, createJSXBuilder(StyleSheet.number)],
        [
            /\b(number|string|boolean|object|instanceof|typeof)\b/g,
            createJSXBuilder(StyleSheet.method),
        ],
        [/(&gt|&lt)/g, createJSXBuilder(StyleSheet.method)],
        [
            /(?<=&lt)\/?(?!number|string|boolean|object|instanceof|typeof)\w*(?=&gt)/g,
            createJSXBuilder(StyleSheet.method),
        ],
        [
            /\b(((?<! )import)|new|true|extends|super|false|from|function|break|case|return|const|=|let|default|((?<! )export)|class)\b/g,
            createJSXBuilder(StyleSheet.keyword),
        ],
    ]
    for (const parserItem of parserTokenMap) {
        matchArray = matchArray.concat(
            getMatchArray(content.slice(), parserItem[0], parserItem[1])
        )
    }
    matchArray = sortMatchArray(matchArray)
    let result = parse(content, matchArray)
    result = result.replace(tab, '<span class="space"></span>')
    result = result.replace(newLine, '<br />')
    return <div dangerouslySetInnerHTML={{ __html: result }} />
}

export function parseTextToJSX(content) {
    const newLine = /\n/g
    return <>{ReactHtmlParser(content.replace(newLine, '<br/>'))}</>
}

utility-functions.js

export function updateArray(arr, index, value) {
    return [...arr.slice(0, index), value, ...arr.slice(index + 1)]
}

export function insertArray(arr, index, value) {
    return [...arr.slice(0, index), value, ...arr.slice(index)]
}

export function replaceString(str, begin, end, value) {
    return str.substring(0, begin) + value + str.substring(end)
}

code.module.css

Contains styles for token classes