Syntax-Highlighting for Java-Code in html

Viewed 817

What I am trying to do

I am working on a syntax-highlighter that highlights java-code in html (example below).

Example output

I've tested the code with the following java-code (caesar-encryption):

//Encryption
public static String encrypt(String text, int n) {
    int x = 0;
    int y = 0;
    String out = ""; //Empty string for result.
    while (x < text.length()) {
        if (text.charAt(x) > 64 && text.charAt(x) < 91) {
            if (text.charAt(x) + n > 90) {
                y = 26;
            }
            out = out + (char) (text.charAt(x) + n - y);
        } else {
            out = out + text.charAt(x);
        }
        x++;
        y = 0;
    }
    return out;
}

The output looks like this:

<!-- Code begins here -->
<div style = "background: LightGray; font: monospace; width: fit-content; height: min-height;"><pre><code>
<span style='color:green; font-weight:bold; '>//Encryption
</span><span style='color:#7f0055; font-weight:bold; '>public</span> <span style='color:#7f0055; font-weight:bold; '>static</span> <span style='color:#7f0055; font-weight:bold; '>String</span> encrypt(<span style='color:#7f0055; font-weight:bold; '>String</span> text, <span style='color:#7f0055; font-weight:bold; '>int</span> n) {
    <span style='color:#7f0055; font-weight:bold; '>int</span> x = 0;
    <span style='color:#7f0055; font-weight:bold; '>int</span> y = 0;
    <span style='color:#7f0055; font-weight:bold; '>String</span> out = <span style='color:blue; font-weight:bold; '>""</span>; <span style='color:green; font-weight:bold; '>//Empty string <span style='color:#7f0055; font-weight:bold; '>for</span> result.
</span>    <span style='color:#7f0055; font-weight:bold; '>while</span> (x < text.length()) {
        <span style='color:#7f0055; font-weight:bold; '>if</span> (text.charAt(x) > 64 && text.charAt(x) < 91) {
            <span style='color:#7f0055; font-weight:bold; '>if</span> (text.charAt(x) + n > 90) {
                y = 26;
            }
            out = out + (<span style='color:#7f0055; font-weight:bold; '>char</span>) (text.charAt(x) + n - y);
        } <span style='color:#7f0055; font-weight:bold; '>else</span> {
            out = out + text.charAt(x);
        }
        x++;
        y = 0;
    }
    <span style='color:#7f0055; font-weight:bold; '>return</span> out;
}
</pre></code></div>
<!-- Code ends here -->

If you open this code with a browser, it looks like this:

enter image description here

The code

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;

public class Gui {

  private JTextArea inputArea;
  private JTextArea outputArea;
  private boolean inputAreaClicked = false;

  public Gui() {
    //JFrame
    JFrame frame = new JFrame("Syntax-Highlighter");
    frame.setSize(700, 700);
    frame.setMinimumSize(new Dimension(700, 700));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    

    //JPanel
    JPanel panel = new JPanel(new GridBagLayout());
    
    //Creating textArea for input
    inputArea = new JTextArea("Enter text here...");
    inputArea.setLineWrap(true);
    inputArea.setTabSize(1);
    inputArea.setFont(new Font("monospaced", Font.ITALIC, 12));
    inputArea.addMouseListener(new MouseAdapter() {     //Set text to "" the first time the inputArea is clicked
      @Override
      public void mouseClicked(MouseEvent e){
        if(!inputAreaClicked) {
          inputArea.setText("");
          inputArea.setFont(new Font("monospaced", Font.PLAIN, 12));
          inputAreaClicked = true;
        }
      }
    });
    JScrollPane input = new JScrollPane(inputArea);
    input.setPreferredSize(new Dimension(600, 110));
    input.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    input.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

    //Creating buttons
    
    JPanel buttonPanel = new JPanel(new FlowLayout(0, 50, 10)); //FlowLayout(align, hgap, vgap)
    JButton button = new JButton("Highlight");
    button.setFont(new Font("Arial", Font.PLAIN, 20));
    button.addActionListener(e -> hightlight(inputArea.getText()));
    button.setPreferredSize(new Dimension(140,30));
   
    //Creating textArea for output
    outputArea = new JTextArea();
    outputArea.setEditable(false);
    outputArea.setTabSize(1);
    outputArea.setLineWrap(false);
    outputArea.setFont(new Font("monospaced", Font.PLAIN, 12));
    JScrollPane output = new JScrollPane(outputArea);
    output.setPreferredSize(new Dimension(600, 400));
    output.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    output.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

    //Layout
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.weighty = 5;
    gbc.gridwidth = GridBagConstraints.REMAINDER;

    //Add everything to panel
    buttonPanel.add(button);
    
    panel.add(input, gbc);
    panel.add(buttonPanel, gbc);
    panel.add(output, gbc);
    
    //Add everything to frame
    frame.add(panel, BorderLayout.CENTER);
    frame.setVisible(true);
  }

  private void hightlight(String text) {
    //Keywords
    String[] array = {
        "abstract", "assert", "boolean", "break", "byte", "case", 
        "catch", "char", "class", "const", "continue", "default", "do", 
        "double", "else", "enum", "extends", "false", "final", "finally", "float",
        "for", "goto", "if", "implements", "import", "instanceof", "int", 
        "interface", "long", "native", "new", "null", "package", "private", "protected",
        "public", "return", "short", "static", "String", "strictfp", "super", "switch", "synchronized", 
        "System", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while"
    };
    
    //Highlight every keyword with color:#7f0055
    for(int i = 0; i < array.length; i++) {
      text = text.replaceAll(array[i] + "(?![a-zA-Z])", "<span style='color:#7f0055; font-weight:bold; '>" + array[i] + "</span>");

    }
    //Highlight Strings
    text = text.replaceAll("\"(?<render>.*?)\"", "<span style='color:blue; font-weight:bold; '>" + "\"${render}\"" + "</span>");
    
    //Highlight import-statements
    text = text.replaceAll("import(?<render>.*?);", "<span style='color:#7f0055; font-weight:bold; '>" + "import${render};" + "</span>");
    
    //Highlight multiline-comments
    Pattern p = Pattern.compile("/\\*(?<render>.*?)\\*/", Pattern.DOTALL);
    Matcher m = p.matcher(text);
    text = m.replaceAll("<span style='color:green; font-weight:bold; '>" + "/\\*${render}\\*/" + "</span>");
    
    //Highlight single-line-comments
    text = text.replaceAll("//(?<render>.*?)\n", "<span style='color:green; font-weight:bold; '>" + "//${render}\n" + "</span>");
    
    text = "<!-- Code begins here -->\n<div style = \"background: LightGray; font: monospace; width: fit-content; height: min-height;\"><pre><code>\n" 
        + text + "\n</pre></code></div>\n<!-- Code ends here -->\n";
    outputArea.setText(text);
  }
}

The problem

As you can see in the picture above, keywords in comments are still highlighted as keywords.

Do you have an idea how this problem can be solved?

What I have tried

I tried to use the following code (replacing in the backreference), but it didn't work:

// Highlight single-line-comments
text = text.replaceAll("//(?<render>.*?)\n", "<span style='color:green; font-weight:bold; '>"
        + "//" + "${render}".replaceAll("color: #7f0055", "color: green") + "\\n</span>");

Unfortunately this isn't working? How can this be corrected?

1 Answers

I'd recommend to iterate over the text, processing one token at a time:

  • Define a list of regular expressions for the different types of input you want to detect, starting with \\G to force a match from the start of the input
  • Add an expression for stuff you want to skip so you make progress even if you don't find anything to highlight
  • Have an outer loop over the content and an inner loop over the list of expressions. For each detected type, remove the matched string from the input, add the replacement to the output and advance the outer loop.

The list in step 1 should probably really be a map to the corresponding HTML replacement string.

P.S. The process is based on tokenization and similar to what Java parsers do at the lowest level -- I don't think you'll need a full Java parser (as one of the comments suggest). Separating the input and output makes sure you don't need to worry about double-processing input and also allows you to HTML-escape the output properly (important as comments and strings might contain HTML).

Related