Thursday, October 15, 2009

curlgen with Groovy BuilderSupport: CurlBuilder for Curl web content markup

Because the Groovy MarkupBuilder has a private method which hardcodes the markup delimiters, a CurlBuilder must extend BuilderSupport just as does MarkupBuilder itself.

What follows looks to be roughly a builder candidate for Curl web content markup using "{" and "}" as the delimiters.  As with XHTML, the Curl content may require a 'prolog' and a few non-hierarchical items comparable to the !DOCTYPE of XML.  Web markup is often more than simply a hierarchy of nested nodes.
/**
* A helper class for creating Curl markup
* borrowed from http://docs.codehaus.org/display/GROOVY/Make+a+builder
* original MarkupBuilder by James Strathan
* mods by RS
*/
public class CurlBuilder extends BuilderSupport {

private IndentPrinter out;
private boolean nospace;
private int state;
private boolean nodeIsEmpty = true;

public CurlBuilder() {
  this(new IndentPrinter());
}

public CurlBuilder(PrintWriter writer) {
  this(new IndentPrinter(writer));
}

public CurlBuilder(Writer writer) {
  this(new IndentPrinter(new PrintWriter(writer)));
}

public CurlBuilder(IndentPrinter out) {
  this.out = out;
}

protected void setParent(Object parent, Object child) {
}

protected Object createNode(Object name) {
  toState(1, name);
  return name;
}

protected Object createNode(Object name, Object value) {
  toState(2, name);
// out.print(">");
  out.print(value.toString());
  return name;
}

protected Object createNode(Object name, Map attributes, Object value) {
  toState(1, name);
  for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
    Map.Entry entry = (Map.Entry) iter.next();
    out.print(" ");
    print(transformName(entry.getKey().toString()));
    out.print("='");
    print(transformValue(entry.getValue().toString()));
    out.print("'");
  }
  if (value != null)
  {
    nodeIsEmpty = false;
    out.print(" " + value + "}");
  }
  return name;
}

protected Object createNode(Object name, Map attributes) {
  return createNode(name, attributes, null);
}

protected void nodeCompleted(Object parent, Object node) {
  toState(3, node);
  out.flush();
}

protected void print(Object node) {
  out.print(node == null ? "null" : node.toString());
}

protected Object getName(String methodName) {
  return super.getName(transformName(methodName));
}

protected String transformName(String name) {
  if (name.startsWith("_")) name = name.substring(1);
    return name.replace('_', '-');
}

protected String transformValue(String value) {
  return value.replaceAll("\\'", """); }

protected void toState(int next, Object name) {
  switch (state) {
    case 0:
      switch (next) {
        case 1:
        case 2:
          out.print("{");
          print(name);
          break;
        case 3:
         throw new Error();
      }
      break;
    case 1:
      switch (next) {
        case 1:
        case 2:
          out.print(" ");
          if (nospace) {
            nospace = false;
          } else {
            out.println();
            out.incrementIndent();
            out.printIndent();
          }
          out.print("{");
          print(name);
          break;
        case 3:
          if (nodeIsEmpty) {
            out.print(" }");
          }
          break;
        }
        break;
      case 2:
        switch (next) {
          case 1:
          case 2:
            throw new Error();
          case 3:
            out.print(" }");
            break;
        }
        break;
      case 3:
        switch (next) {
          case 1:
          case 2:
            if (nospace) {
              nospace = false;
            } else {
              out.println();
              out.printIndent();
            }
            out.print("{");
            print(name);
            break;
          case 3:
            if (nospace) {
              nospace = false;
            } else {
              out.println();
              out.decrementIndent();
              out.printIndent();
            }
            out.print(" }");
            break;
        }
        break;
     }
     state = next;
   }
}
That could have been so elegant if the MarkupBuilder class had understood markup itself as a pattern rather than hard-coding the SGML-style tag delimiters and assuming the tagging style (close before content, reopen after content.)

The ability to inject a markup builder for non-SGML markup for an entire site is lost: we still have to look into such things as 404 and 500 errors because we have only covered where we explicitly instantiate a builder.  DI should have solved that web container-wide.

As I never tire of saying, when we get beyond HTML5 and the mere browser, this SGML-bias will look like an unquestioned assumption as curious as any other in the history of data format oddities and over-sights.

To get from   <br/>  to {br} should not be a coding intervention task of this magnitude or this nature.

No comments: