A simple templater in Java

This is going to be a quick one, at least in terms of commentary.

This site is 100% static HTML. This is because I don't want to spend the rest of my life patching Wordpress. I know there are many other alternatives but I'm perfectly happy to keep this static HTML. Way, way back in the early days (2000ish) I managed this site in Microsoft FrontPage but ultimately it was still just static HTML.

Despite that, I have a bunch of code to maintain the site. Eventually I'll post about it in more detail. It handles things like rewriting the header, footer, and navigation to all HTML pages whenever I make a change.

For the longest time I had stuff like CSS versions hardcoded. That is not good but I only run this on my PC so who cares? I'd like to post my little web publishing system on Github eventually though. I need to fix bad things like this so I don't look inept (I know, too late).

I'm aware of multiple libraries to handle templates but I decided to write my own because it's very easy. The requirements go like:

Here's the code I came up with:


/* MIT license */

import java.util.HashMap;
import java.util.Map;

public class SimpleTemplater{
  //default delimiter
  public final static String DEFAULT_TEMPLATE_START="{";
  public final static String DEFAULT_TEMPLATE_END="}";

  private Map<String,String> staticTemplates;
  private String templateStart=DEFAULT_TEMPLATE_START;
  private String templateEnd=DEFAULT_TEMPLATE_END;
  
  public SimpleTemplater(Map<String,String> staticTemplates){
    this.staticTemplates=staticTemplates;
  }

  public SimpleTemplater(Map<String,String> staticTemplates,String templateStart,String templateEnd){
    this.staticTemplates=staticTemplates;
    this.templateStart=templateStart;
    this.templateEnd=templateEnd;
  }
  
  public String process(String s){
    StringBuffer sb=new StringBuffer(s);
    int startIndex=sb.indexOf(this.templateStart);
    int endIndex=sb.indexOf(this.templateEnd);
    while(
    (startIndex>=0)&&
    (startIndex<sb.length())&&
    (endIndex>0)&&
    (endIndex<sb.length())
    )
    {
      if(endIndex>startIndex){
        //we have something
        String match=sb.substring(startIndex,endIndex+1);
        //test if this is a static template
        String staticReplace=this.staticTemplates.getOrDefault(match,null);
        if(staticReplace!=null){
          //replace the template with the value
          sb.replace(startIndex,endIndex+1,staticReplace);
          //next starting point should be at the end of the block just replaced
          startIndex=startIndex+staticReplace.length();
          endIndex=startIndex+1;
        }else{
          //increment indexes
          startIndex++;
          endIndex=startIndex+1;
        }
      }else{ //endIndex<startIndex so there is a mismatched tag
        endIndex=startIndex+1;
      }
      //search for next template match
      startIndex=sb.indexOf(this.templateStart,startIndex);
      endIndex=sb.indexOf(this.templateEnd,endIndex);
    }
    return(sb.toString());
  }
}

This is probably (certainly) not the most efficient solution to the problem. My entire web publisher app, which includes this code, runs against every page in this site in roughly 1 second. If it ever takes 2 seconds then I'll look into optimizing it. It's a very simple scheme to swap string values which is what I needed.

Here is a JUnit test for this code:


/* also MIT license */
class TestSimpleTemplater{
  @Test
  void testStaticTemplates(){
    //setup some data
    Map<String,String> staticTemplates=new HashMap<String,String>();
    staticTemplates.put("{TESTKEY1}","TESTVALUE1");
    staticTemplates.put("{TESTKEY2}","TESTVALUE2");
    staticTemplates.put("{TESTKEY3}","TESTVALUE3");
    //setup templater
    SimpleTemplater templater=new SimpleTemplater(staticTemplates);
    //simple tests
    String templateString="{TESTKEY1}";
    String expectedResult="TESTVALUE1";
    String resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="{TESTKEY1";
    expectedResult="{TESTKEY1";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="{XSS_VERSION}";
    expectedResult="{XSS_VERSION}";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="blah{TESTKEY1}blah";
    expectedResult="blahTESTVALUE1blah";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="{TESTKEY2}";
    expectedResult="TESTVALUE2";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="{TESTKEY3}";
    expectedResult="TESTVALUE3";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    //longer tests
    templateString="blah{TESTKEY1}blah{TESTKEY2}blah{TESTKEY3}";
    expectedResult="blahTESTVALUE1blahTESTVALUE2blahTESTVALUE3";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="blah{TESTKEY1blah{TESTKEY2}blah{TESTKEY3}";
    expectedResult="blah{TESTKEY1blahTESTVALUE2blahTESTVALUE3";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="blah{TESTKEY1}blahTESTKEY2}blah{TESTKEY3}";
    expectedResult="blahTESTVALUE1blahTESTKEY2}blahTESTVALUE3";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    //oddball tests
    templateString="blah{{TESTKEY1}blah";
    expectedResult="blah{TESTVALUE1blah";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="blah{TESTKEY1}}blah";
    expectedResult="blahTESTVALUE1}blah";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
    templateString="blah{{TESTKEY1}}blah";
    expectedResult="blah{TESTVALUE1}blah";
    resultString=templater.process(templateString);
    assertEquals(expectedResult,resultString);
  }
}

The latest version of this code will be here. It's possible I will make it more efficient or more complicated.



Related