package ognl.helperfunction; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ognl.webobjects.WOOgnl; import com.webobjects.appserver.WOAssociation; import com.webobjects.appserver.WOElement; import com.webobjects.appserver._private.WOConstantValueAssociation; import com.webobjects.appserver._private.WODeclaration; import com.webobjects.appserver._private.WOHTMLAttribute; import com.webobjects.appserver._private.WOHTMLCommentString; import com.webobjects.appserver._private.WOKeyValueAssociation; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableDictionary; public class WOHelperFunctionParser { private static final Logger log = LoggerFactory.getLogger(WOHelperFunctionParser.class); public static boolean _debugSupport; private static String WO_REPLACEMENT_MARKER = "__REPL__"; private WOHTMLWebObjectTag _currentWebObjectTag; private NSMutableDictionary _declarations; private int _inlineBindingCount; private String _declarationString; private String _HTMLString; private NSArray _languages; public WOHelperFunctionParser(String htmlString, String declarationString, NSArray languages) { _HTMLString = htmlString; _declarationString = declarationString; _languages = languages; _declarations = null; _currentWebObjectTag = new WOHTMLWebObjectTag(); } public WOElement parse() throws WOHelperFunctionDeclarationFormatException, WOHelperFunctionHTMLFormatException, ClassNotFoundException { parseDeclarations(); for (Enumeration e = declarations().objectEnumerator(); e.hasMoreElements();) { WODeclaration declaration = (WODeclaration) e.nextElement(); processDeclaration(declaration); } WOElement woelement = parseHTML(); return woelement; } public void didParseOpeningWebObjectTag(String s, WOHelperFunctionHTMLParser htmlParser) throws WOHelperFunctionHTMLFormatException { if (WOHelperFunctionTagRegistry.allowInlineBindings()) { int spaceIndex = s.indexOf(' '); int colonIndex; if (spaceIndex != -1) { colonIndex = s.substring(0, spaceIndex).indexOf(':'); } else { colonIndex = s.indexOf(':'); } if (colonIndex != -1) { WODeclaration declaration = parseInlineBindings(s, colonIndex); s = "<wo name = \"" + declaration.name() + "\""; } } _currentWebObjectTag = new WOHTMLWebObjectTag(s, _currentWebObjectTag); log.debug("Inserted WebObject with Name '{}'.", _currentWebObjectTag.name()); } public void didParseClosingWebObjectTag(String s, WOHelperFunctionHTMLParser htmlParser) throws WOHelperFunctionDeclarationFormatException, WOHelperFunctionHTMLFormatException, ClassNotFoundException { WOHTMLWebObjectTag webobjectTag = _currentWebObjectTag.parentTag(); if (webobjectTag == null) { throw new WOHelperFunctionHTMLFormatException("<" + getClass().getName() + "> Unbalanced WebObject tags. Either there is an extra closing </WEBOBJECT> tag in the html template, or one of the opening <WEBOBJECT ...> tag has a typo (extra spaces between a < sign and a WEBOBJECT tag ?)."); } try { WOElement element = _currentWebObjectTag.dynamicElement(_declarations, _languages); _currentWebObjectTag = webobjectTag; _currentWebObjectTag.addChildElement(element); } catch (RuntimeException e) { throw new RuntimeException("Unable to load the component named '" + componentName(_currentWebObjectTag) + "' with the declaration " + prettyDeclaration((WODeclaration) _declarations.objectForKey(_currentWebObjectTag.name())) + ". Make sure the .wo folder is where it's supposed to be and the name is spelled correctly.", e); } } public void didParseComment(String comment, WOHelperFunctionHTMLParser htmlParser) { WOHTMLCommentString wohtmlcommentstring = new WOHTMLCommentString(comment); _currentWebObjectTag.addChildElement(wohtmlcommentstring); } public void didParseText(String text, WOHelperFunctionHTMLParser htmlParser) { _currentWebObjectTag.addChildElement(text); } protected WODeclaration parseInlineBindings(String tag, int colonIndex) throws WOHelperFunctionHTMLFormatException { StringBuffer keyBuffer = new StringBuffer(); StringBuffer valueBuffer = new StringBuffer(); StringBuffer elementTypeBuffer = new StringBuffer(); NSMutableDictionary associations = new NSMutableDictionary(); StringBuffer currentBuffer = elementTypeBuffer; boolean changeBuffers = false; boolean inQuote = false; int length = tag.length(); for (int index = colonIndex + 1; index < length; index++) { char ch = tag.charAt(index); if (!inQuote && (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { changeBuffers = true; } else if (!inQuote && ch == '=') { changeBuffers = true; } else if (inQuote && ch == '\\') { index++; if (index == length) { throw new WOHelperFunctionHTMLFormatException("'" + tag + "' has a '\\' as the last character."); } if (tag.charAt(index) == '\"') { currentBuffer.append("\""); } else if (tag.charAt(index) == 'n') { currentBuffer.append('\n'); } else if (tag.charAt(index) == 'r') { currentBuffer.append('\r'); } else if (tag.charAt(index) == 't') { currentBuffer.append('\t'); } else { currentBuffer.append('\\'); currentBuffer.append(tag.charAt(index)); } } else { if (changeBuffers) { if (currentBuffer == elementTypeBuffer) { currentBuffer = keyBuffer; } else if (currentBuffer == keyBuffer) { currentBuffer = valueBuffer; } else if (currentBuffer == valueBuffer) { parseInlineAssociation(keyBuffer, valueBuffer, associations); currentBuffer = keyBuffer; } currentBuffer.setLength(0); changeBuffers = false; } if (ch == '"') { inQuote = !inQuote; } currentBuffer.append(ch); } } if (inQuote) { throw new WOHelperFunctionHTMLFormatException("'" + tag + "' has a quote left open."); } if (keyBuffer.length() > 0) { if (valueBuffer.length() > 0) { parseInlineAssociation(keyBuffer, valueBuffer, associations); } else { throw new WOHelperFunctionHTMLFormatException("'" + tag + "' defines a key but no value."); } } String elementType = elementTypeBuffer.toString(); String shortcutType = (String) WOHelperFunctionTagRegistry.tagShortcutMap().objectForKey(elementType); if (shortcutType != null) { elementType = shortcutType; } else if (elementType.startsWith(WO_REPLACEMENT_MARKER)) { // Acts only on tags, where we have "dynamified" inside the tag parser // this takes the value found after the "wo:" part in the element and generates a WOGenericContainer with that value // as the elementName binding elementType = elementType.replaceAll(WO_REPLACEMENT_MARKER, ""); associations.setObjectForKey(WOHelperFunctionAssociation.associationWithValue(elementType), "elementName"); elementType = "WOGenericContainer"; } String elementName; synchronized (this) { elementName = "_" + elementType + "_" + _inlineBindingCount; _inlineBindingCount++; } WOTagProcessor tagProcessor = (WOTagProcessor) WOHelperFunctionTagRegistry.tagProcessorMap().objectForKey(elementType); WODeclaration declaration; if (tagProcessor == null) { declaration = WOHelperFunctionParser.createDeclaration(elementName, elementType, associations); } else { declaration = tagProcessor.createDeclaration(elementName, elementType, associations); } _declarations.setObjectForKey(declaration, elementName); processDeclaration(declaration); return declaration; } protected void parseInlineAssociation(StringBuffer keyBuffer, StringBuffer valueBuffer, NSMutableDictionary bindings) throws WOHelperFunctionHTMLFormatException { String key = keyBuffer.toString().trim(); String value = valueBuffer.toString().trim(); NSDictionary quotedStrings; if (value.startsWith("\"")) { value = value.substring(1); if (value.endsWith("\"")) { value = value.substring(0, value.length() - 1); } else { throw new WOHelperFunctionHTMLFormatException(valueBuffer + " starts with quote but does not end with one."); } if (value.startsWith("$")) { value = value.substring(1); if (value.endsWith("VALID")) { value = value.replaceFirst("\\s*//\\s*VALID", ""); } quotedStrings = new NSDictionary(); } else { value = value.replaceAll("\\\\\\$", "\\$"); value = value.replaceAll("\\\"", "\""); quotedStrings = new NSDictionary(value, "_WODP_0"); value = "_WODP_0"; } } else { quotedStrings = new NSDictionary(); } WOAssociation association = WOHelperFunctionDeclarationParser._associationWithKey(value, quotedStrings); bindings.setObjectForKey(association, key); } protected void processDeclaration(WODeclaration declaration) { NSMutableDictionary associations = (NSMutableDictionary) declaration.associations(); Enumeration bindingNameEnum = associations.keyEnumerator(); while (bindingNameEnum.hasMoreElements()) { String bindingName = (String) bindingNameEnum.nextElement(); WOAssociation association = (WOAssociation) associations.valueForKey(bindingName); WOAssociation helperAssociation = parserHelperAssociation(association); if (helperAssociation != association) { associations.setObjectForKey(helperAssociation, bindingName); } } // This will replace constant associations with ognl associations // when needed. WOOgnl.factory().convertOgnlConstantAssociations(associations); } protected WOAssociation parserHelperAssociation(WOAssociation originalAssociation) { WOAssociation association = originalAssociation; String originalKeyPath = null; if (association instanceof WOKeyValueAssociation) { WOKeyValueAssociation kvAssociation = (WOKeyValueAssociation) association; originalKeyPath = kvAssociation.keyPath(); } // else if (association instanceof WOConstantValueAssociation) { // WOConstantValueAssociation constantAssociation = // (WOConstantValueAssociation) association; // Object constantValue = constantAssociation.valueInComponent(null); // if (constantValue instanceof String) { // originalKeyPath = (String) constantValue; // } // } if (originalKeyPath != null) { int pipeIndex = originalKeyPath.indexOf('|'); if (pipeIndex != -1) { String targetKeyPath = originalKeyPath.substring(0, pipeIndex).trim(); String frameworkName = WOHelperFunctionRegistry.APP_FRAMEWORK_NAME; String helperFunctionName = originalKeyPath.substring(pipeIndex + 1).trim(); String otherParams = null; int openParenIndex = helperFunctionName.indexOf('('); if (openParenIndex != -1) { int closeParenIndex = helperFunctionName.indexOf(')', openParenIndex + 1); otherParams = helperFunctionName.substring(openParenIndex + 1, closeParenIndex); helperFunctionName = helperFunctionName.substring(0, openParenIndex); } int helperFunctionDotIndex = helperFunctionName.indexOf('.'); if (helperFunctionDotIndex != -1) { frameworkName = helperFunctionName.substring(0, helperFunctionDotIndex); helperFunctionName = helperFunctionName.substring(helperFunctionDotIndex + 1); } StringBuilder ognlKeyPath = new StringBuilder(); ognlKeyPath.append('~'); ognlKeyPath.append("@" + WOHelperFunctionRegistry.class.getName() + "@registry()._helperInstanceForFrameworkNamed(#this, \""); ognlKeyPath.append(helperFunctionName); ognlKeyPath.append("\", \""); ognlKeyPath.append(targetKeyPath); ognlKeyPath.append("\", \""); ognlKeyPath.append(frameworkName); ognlKeyPath.append("\")."); ognlKeyPath.append(helperFunctionName); ognlKeyPath.append('('); ognlKeyPath.append(targetKeyPath); if (otherParams != null) { ognlKeyPath.append(','); ognlKeyPath.append(otherParams); } ognlKeyPath.append(')'); log.debug("Converted {} into {}", originalKeyPath, ognlKeyPath); association = new WOConstantValueAssociation(ognlKeyPath.toString()); } } return association; } protected String prettyDeclaration(WODeclaration declaration) { StringBuilder declarationStr = new StringBuilder(); if (declaration == null) { declarationStr.append("[none]"); } else { declarationStr.append("Component Type = " + declaration.type()); declarationStr.append(", Bindings = { "); Enumeration keyEnum = declaration.associations().keyEnumerator(); while (keyEnum.hasMoreElements()) { String key = (String) keyEnum.nextElement(); Object assoc = declaration.associations().objectForKey(key); if (assoc instanceof WOKeyValueAssociation) { declarationStr.append(key + "=" + ((WOKeyValueAssociation) assoc).keyPath()); } else if (assoc instanceof WOConstantValueAssociation) { declarationStr.append(key + "='" + ((WOConstantValueAssociation) assoc).valueInComponent(null) + "'"); } else { declarationStr.append(key + "=" + assoc); } if (keyEnum.hasMoreElements()) { declarationStr.append(", "); } } declarationStr.append(" }"); } return declarationStr.toString(); } private WOElement parseHTML() throws WOHelperFunctionHTMLFormatException, WOHelperFunctionDeclarationFormatException, ClassNotFoundException { WOElement currentWebObjectTemplate = null; if (_HTMLString != null && _declarations != null) { WOHelperFunctionHTMLParser htmlParser = new WOHelperFunctionHTMLParser(this, _HTMLString); htmlParser.parseHTML(); String webobjectTagName = _currentWebObjectTag.name(); if (webobjectTagName != null) { throw new WOHelperFunctionHTMLFormatException("There is an unbalanced WebObjects tag named '" + webobjectTagName + "'."); } currentWebObjectTemplate = _currentWebObjectTag.template(); } return currentWebObjectTemplate; } protected boolean isInline(WOHTMLWebObjectTag tag) { String name = tag.name(); return name != null && name.startsWith("_") && name.length() > 1 && name.indexOf('_', 1) != -1; } protected String componentName(WOHTMLWebObjectTag tag) { String name = tag.name(); // This goofiness reparses back out inline binding names if (name == null) { name = "[none]"; } else if (isInline(tag)) { int secondUnderscoreIndex = name.indexOf('_', 1); if (secondUnderscoreIndex != -1) { name = name.substring(1, secondUnderscoreIndex); } } return name; } public String declarationString() { return _declarationString; } public void setDeclarationString(String value) { _declarationString = value; } public NSMutableDictionary declarations() { return _declarations; } public void setDeclarations(NSMutableDictionary value) { _declarations = value; } private void parseDeclarations() throws WOHelperFunctionDeclarationFormatException { if (_declarations == null && _declarationString != null) { _declarations = WOHelperFunctionDeclarationParser.declarationsWithString(_declarationString); } } public static WODeclaration createDeclaration(String declarationName, String declarationType, NSMutableDictionary associations) { WODeclaration declaration = new WODeclaration(declarationName, declarationType, associations); if (WOHelperFunctionParser._debugSupport && associations != null && associations.objectForKey(WOHTMLAttribute.Debug) == null) { //associations.setObjectForKey(new WOConstantValueAssociation(Boolean.TRUE), WOHTMLAttribute.Debug); Enumeration associationsEnum = associations.keyEnumerator(); while (associationsEnum.hasMoreElements()) { String bindingName = (String) associationsEnum.nextElement(); WOAssociation association = (WOAssociation) associations.objectForKey(bindingName); association.setDebugEnabledForBinding(bindingName, declarationName, declarationType); association._setDebuggingEnabled(false); } } return declaration; } }