Java tutorial
/** * Sencha GXT 4.0.0 - Sencha for GWT * Copyright (c) 2006-2015, Sencha Inc. * * licensing@sencha.com * http://www.sencha.com/products/gxt/license/ * * ================================================================================ * Open Source License * ================================================================================ * This version of Sencha GXT is licensed under the terms of the Open Source GPL v3 * license. You may use this license only if you are prepared to distribute and * share the source code of your application under the GPL v3 license: * http://www.gnu.org/licenses/gpl.html * * If you are NOT prepared to distribute and share the source code of your * application under the GPL v3 license, other commercial and oem licenses * are available for an alternate download of Sencha GXT. * * Please see the Sencha GXT Licensing page at: * http://www.sencha.com/products/gxt/license/ * * For clarification or additional options, please contact: * licensing@sencha.com * ================================================================================ * * * ================================================================================ * Disclaimer * ================================================================================ * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY, * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING. * ================================================================================ */ package com.sencha.gxt.core.rebind; import java.util.EmptyStackException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringEscapeUtils; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; import com.sencha.gxt.core.rebind.XTemplateParser.ContentChunk.ContentType; /** * Parses out the contents of a given XTemplate, with a result intended for use * in one or more SafeHtmlTemplates interfaces, to handle the html replacement * issues. * * */ public class XTemplateParser { private static final Pattern INVOKE_PATTERN = Pattern.compile("\\{\\[([^\\]]+)\\]\\}"); private static final Pattern PARAM_PATTERN = Pattern .compile("\\{((?:[a-zA-Z_0-9\\.]+|#)(?:\\:[^\\}\\(]+(?:\\([^\\)]+\\))?)?)\\}"); private static final Pattern TAG_PATTERN = Pattern.compile("<tpl([^>]+)>"); private static final Pattern TAG_CLOSE_PATTERN = Pattern.compile("</tpl>"); private static final Pattern NON_LITERAL_PATTERN = Pattern.compile(PARAM_PATTERN.pattern() + "|" + TAG_PATTERN.pattern() + "|" + TAG_CLOSE_PATTERN.pattern() + "|" + INVOKE_PATTERN); private static final Pattern ATTR_PATTERN = Pattern.compile("(if|for)=(?:\"([^\">]+)\"|\\'([^\\'>]+)\\')"); private final TreeLogger logger; public XTemplateParser(TreeLogger logger) { this.logger = logger; } public TemplateModel parse(String template) throws UnableToCompleteException { // look for parameters or tags (Consider combining into one pattern) TemplateModel model = new TemplateModel(); Stack<ContainerTemplateChunk> stack = new Stack<ContainerTemplateChunk>(); stack.push(model); Matcher m = NON_LITERAL_PATTERN.matcher(template); int lastMatchEnd = 0; while (m.find()) { // range of the current non-literal int begin = m.start(), end = m.end(); String currentMatch = template.substring(begin, end); // if there was content since the last non-literal chunk, track it if (lastMatchEnd < begin) { ContentChunk c = literal(template.substring(lastMatchEnd, begin)); stack.peek().children.add(c); log(c); } // move the last match pointer lastMatchEnd = end; // tpl tag starting Matcher tagOpenMatch = TAG_PATTERN.matcher(currentMatch); if (tagOpenMatch.matches()) { ControlChunk c = new ControlChunk(); c.controls = new HashMap<String, String>(); String attrs = tagOpenMatch.group(1).trim(); Matcher attrMatcher = ATTR_PATTERN.matcher(attrs); while (attrMatcher.find()) { // should be if or for String key = attrMatcher.group(1); // must be html-decoded String encodedValue = attrMatcher.group(2) == null ? attrMatcher.group(3) : attrMatcher.group(2); String value = StringEscapeUtils.unescapeXml(encodedValue); c.controls.put(key, value); } stack.peek().children.add(c); stack.push(c); log(c); continue; } // tpl tag ending Matcher tagCloseMatch = TAG_CLOSE_PATTERN.matcher(currentMatch); if (tagCloseMatch.matches()) { TemplateChunk c; try { c = stack.pop(); } catch (EmptyStackException ex) { logger.log(Type.ERROR, "Too many </tpl> tags"); throw new UnableToCompleteException(); } log(c); continue; } // reference (code) Matcher codeMatch = INVOKE_PATTERN.matcher(currentMatch); if (codeMatch.matches()) { ContentChunk c = new ContentChunk(); c.type = ContentType.CODE; c.content = codeMatch.group(1); stack.peek().children.add(c); log(c); continue; } // reference (param) Matcher paramMatch = PARAM_PATTERN.matcher(currentMatch); if (paramMatch.matches()) { ContentChunk c = new ContentChunk(); c.type = ContentType.REFERENCE; c.content = paramMatch.group(1); stack.peek().children.add(c); log(c); continue; } } // handle trailing content if (lastMatchEnd < template.length()) { ContentChunk c = literal(template.substring(lastMatchEnd)); log(c); model.children.add(c); } if (model != stack.peek()) { logger.log(Type.ERROR, "Too few </tpl> tags"); throw new UnableToCompleteException(); } return model; } private ContentChunk literal(String match) { // Get the content, and crop out whitespace between tags // TODO provide a way to turn this functionality on and off ContentChunk c = new ContentChunk(); c.content = match.replaceAll("\\>\\s+\\<", "><"); c.type = ContentType.LITERAL; return c; } private void log(TemplateChunk c) { if (c instanceof ContentChunk) { logger.log(Type.DEBUG, "xtemplate[" + ((ContentChunk) c).type + "]:\t" + ((ContentChunk) c).content); } else if (c instanceof ContainerTemplateChunk) { ContainerTemplateChunk cont = (ContainerTemplateChunk) c; if (cont.children.size() == 0) { logger.log(Type.DEBUG, "xtemplate[container start]"); } else { logger.log(Type.DEBUG, "xtemplate[container end]"); } } } public static class TemplateModel extends ContainerTemplateChunk { @Override public String toString() { return "Root of template"; } } public static class ControlChunk extends ContainerTemplateChunk { public Map<String, String> controls; @Override public String toString() { return "<tpl> tag: " + controls; } } public static class ContentChunk extends TemplateChunk { public enum ContentType { LITERAL, REFERENCE, CODE } public ContentType type; public String content; @Override public String toString() { return content; } } public abstract static class TemplateChunk { } public abstract static class ContainerTemplateChunk extends TemplateChunk { public final List<TemplateChunk> children = new LinkedList<TemplateChunk>(); } }