Reputation: 1015
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
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
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
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