Mikael S.
Mikael S.

Reputation: 1015

Design Pattern for generating HTML Tags

For a small system that can get a String and output an HTML, what design pattern would be suitable?

This is a small example:

public String makeStrong(String in) {

    return "<strong>" + in + "</strong>";
}

Of course it needs to model some hierarchical structure so that it can fit <ul> and <ol> and their children. I'm thinking of Decorator but Composite pattern sounds good too. What should I consider?

Upvotes: 6

Views: 1877

Answers (3)

Ali
Ali

Reputation: 58501

A minimalistic approach would be the following, using the AutoCloseable interface:

The program prints
<body><strong> some text </strong><ol><li>a</li><li>b</li><li>c</li></ol></body>
Online demo

public class Main {

    public static void main(String args[]) {
        Page page = new Page();
        writeBody(page);
        page.show();

    }

    static void writeBody(Page page) {
        try (BodyTag bodyTag = new BodyTag(page)) {
            writeText(page);
            writeList(page);
        }
    }

    static void writeText(Page page) {
        try (StrongTag strongTag = new StrongTag(page)) {
            strongTag.append(" some text ");
        }
    }

    static void writeList(Page page) {
        try (ListTag list = new ListTag(page)) {
            // A twisted way to write a, b and c as elements
            for (char c=97; c<100; ++c) {
                appendElem(page, String.valueOf(c));
            }
        }
    }

    static void appendElem(Page page, String elem) {
        try (ListElem li = new ListElem(page)) {
            li.append(elem);
        }        
    }
}

class Page {

    StringBuilder content = new StringBuilder();

    void append(String text) {
        content.append(text);
    }

    void show() {
        System.out.println(content);
    }
}

abstract class Tag implements AutoCloseable {

    String name;
    Page content;

    Tag(Page parent, String tagName) {
        name = tagName;
        content = parent;
        content.append("<");
        content.append(name);
        content.append(">");        
    }

    public void append(String text) {
        content.append(text);
    }

    @Override
    final public void close() {
        content.append("</");
        content.append(name);
        content.append(">");
    }
}

final class StrongTag extends Tag {

    public StrongTag(Page parent) {
        super(parent, "strong");
    }
}

final class BodyTag extends Tag {

    public BodyTag(Page parent) {
        super(parent, "body");
    }
}

final class ListTag extends Tag {

    public ListTag(Page parent) {
        super(parent, "ol");
    }
}

final class ListElem extends Tag {

    public ListElem(Page parent) {
        super(parent, "li");
    }
}

Definitely not idiot-proof but for simple uses, it might serve as a good start.

Upvotes: 2

Joop Eggen
Joop Eggen

Reputation: 109597

On first glance I thought of a Builder pattern or better a fluent API. A bit more compact is the following:

import static a.b.c.HTML.*;

    String html = p(
                ol(
                    li(),
                    li(
                        _("Hello, "),
                        strong(_("World")),
                        _("!")
                    ),
                    li()
                )
            ).toString();


public class HTML {

    protected final String tag;
    private final HTML[] items;

    public HTML(String tag, final HTML... items) {
        this.tag = tag;
        this.items = items;
    }

    public static HTML _(String text) {
        return new HTML(text) {

            @Override
            public String toString() {
                return tag;
            }

            @Override
            protected void buildString(StringBuilder sb) {
                sb.append(tag);
            }
       };
    }

    public static HTML li(final HTML... items) {
        return new HTML("li", items);
    }

    public static HTML ol(final HTML... items) {
        return new HTML("ol", items);
    }

    public static HTML p(final HTML... items) {
        return new HTML("p", items);
    }

    public static HTML strong(final HTML... items) {
        return new HTML("strong", items);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        buildString(sb);
        return sb.toString();
    }

    protected void buildString(StringBuilder sb) {
        sb.append('<').append(tag);
        if (items.length == 0) {
            sb.append(" />"); 
        } else {
            sb.append('>');
            for (HTML item : items) {
                item.buildString(sb);
            }
            sb.append("</").append(tag).append('>');
        }

    }
}

Upvotes: 4

nicktalbot
nicktalbot

Reputation: 416

Consider the builder pattern for building validated HTML fragments. I wrote something similar to validate HTML for use in Swing app components, e.g. tooltips. Here's a rough skeleton of something you could build on:

public final class HtmlBuilder()
{
  private final StringBuilder sb = new StringBuilder();
  private final Deque tagStack = new ArrayDeque();  

  public HtmlBuilder()
  {
    startTag("html");
  }

  // Consider using an enum of valid tags and extending to support attributes
  public HtmlBuilder startTag( String tag )
  {
    // TODO preconditions
    tagStack.push( tag );
    sb.append('<').append(tag).append('>');
    return this;
  }

  public HtmlBuilder endTag( String tag )
  {
    // TODO preconditions,
    // e.g. check "!tagStack.isEmpty() && tagStack.peek().equals( tag )
    tagStack.pop();
    sb.append('<').append('/').append(tag).append('>');
    return this;
  }

  public HtmlBuilder append( String text )
  {
    // TODO Preconditions, check for/escape special characters etc
    sb.append( text );
    return this;
  }

  @Override
  public String toString()
  {
    endTag("html")
    return sb.toString();
  }
}

Simple use case:

String html = new HtmlBuilder()
    .startTag( "strong" ).append( "text" ).endTag( "strong" )
    .toString();

You could add as much validation as you like, e.g. defining "tr" as only being allowed inside a "table". I suspect there is something like this out there already.

Upvotes: 3

Related Questions