diff --git a/src/main/java/de/neuland/pug4j/parser/node/AttrsNode.java b/src/main/java/de/neuland/pug4j/parser/node/AttrsNode.java index b56aad2..0c52d14 100644 --- a/src/main/java/de/neuland/pug4j/parser/node/AttrsNode.java +++ b/src/main/java/de/neuland/pug4j/parser/node/AttrsNode.java @@ -6,7 +6,6 @@ import com.google.gson.Gson; import de.neuland.pug4j.exceptions.ExpressionException; import de.neuland.pug4j.exceptions.PugCompilerException; -import de.neuland.pug4j.expression.ExpressionHandler; import de.neuland.pug4j.model.PugModel; import de.neuland.pug4j.template.PugTemplate; import org.apache.commons.lang3.ArrayUtils; @@ -84,28 +83,33 @@ public boolean hasCodeNode() { } protected String visitAttributes(PugModel model, PugTemplate template) { - LinkedList newAttributes = new LinkedList(attributes); - if(attributeBlocks.size()>0){ - for (String attributeBlock : attributeBlocks) { - Object o = null; - try { - o = template.getExpressionHandler().evaluateExpression(attributeBlock, model); - } catch (ExpressionException e) { - throw new PugCompilerException(this, template.getTemplateLoader(), e); - } - if(o instanceof Map) { - Map map = (Map) o; - for (Map.Entry entry : map.entrySet()) { - Attr attr = new Attr(String.valueOf(entry.getKey()),entry.getValue(),false); - newAttributes.add(attr); - } - } + LinkedList attributesList = new LinkedList(attributes); + //if attributes block than add to attributes from tag + if(attributeBlocks.size()>0) { + for (String attributeBlockExpression : attributeBlocks) { + addAttributesBlockToAttributesList(model, template, attributeBlockExpression, attributesList); + } + } + Map attrs = attrs(model, template, attributesList); + return attrsToString(attrs); + } + + private void addAttributesBlockToAttributesList(final PugModel model, final PugTemplate template, final String attributeBlockExpression, final LinkedList newAttributes) { + Object attributesBlock = null; + try { + attributesBlock = template.getExpressionHandler().evaluateExpression(attributeBlockExpression, model); + } catch (ExpressionException e) { + throw new PugCompilerException(this, template.getTemplateLoader(), e); + } + if (attributesBlock instanceof Map) { + Map map = (Map) attributesBlock; + for (Map.Entry entry : map.entrySet()) { + //Attributes applied using &attributes are not automatically escaped. You must be sure to sanitize any user inputs to avoid cross-site scripting (XSS). If passing in attributes from a mixin call, this is done automatically. + Attr attr = new Attr(String.valueOf(entry.getKey()), entry.getValue(), false); + newAttributes.add(attr); } - Map attrs = attrs(model, template,newAttributes); - return attrsToString(attrs); }else{ - Map attrs = attrs(model, template, newAttributes); - return attrsToString(attrs); + throw new PugCompilerException(this, template.getTemplateLoader(), "attribute block '" + attributeBlockExpression + "' is not a Map"); } } @@ -126,190 +130,180 @@ private String attrsToString(Map attrs) { protected Map attrs(PugModel model, PugTemplate template, LinkedList attrs) { ArrayList classes = new ArrayList<>(); ArrayList classEscaping = new ArrayList<>(); - Map newAttributes = new LinkedHashMap<>(); + Map normalAttributes = new LinkedHashMap<>(); for (Attr attribute : attrs) { - try { - addAttributesToMap(newAttributes,classes,classEscaping, attribute, model, template); - } catch (ExpressionException e) { - throw new PugCompilerException(this, template.getTemplateLoader(), e); - } + createAttributeValues(normalAttributes, classes, classEscaping, attribute, model, template); } + //Put class as the first attribute Map finalAttributes = new LinkedHashMap<>(); if(!classes.isEmpty()){ - String out = ""; - for (int i = 0; i < classes.size(); i++) { - String classname; - if(classEscaping.get(i)) - classname = StringEscapeUtils.escapeHtml4(classes.get(i)); - else - classname = classes.get(i); - - if(i==0) - out = classname; - else - out = out + " " + classname; - } - finalAttributes.put("class", out); + final String classList = renderClassList(classes, classEscaping); + finalAttributes.put("class", classList); } - finalAttributes.putAll(newAttributes); + finalAttributes.putAll(normalAttributes); return finalAttributes; } - private void addAttributesToMap(Map newAttributes, ArrayList classes, ArrayList classEscaping, Attr attribute, PugModel model, PugTemplate template) throws ExpressionException { - String name = attribute.getName(); + private String renderClassList(final ArrayList classes, final ArrayList classEscaping) { + final StringBuilder classList = new StringBuilder(); + for (int i = 0; i < classes.size(); i++) { + final String className; + final Boolean escaped = classEscaping.get(i); + + if(escaped) + className = StringEscapeUtils.escapeHtml4(classes.get(i)); + else + className = classes.get(i); + + if(i>0) + classList.append(" "); + classList.append(className); + } + return classList.toString(); + } + + private void createAttributeValues(Map newAttributes, ArrayList classes, ArrayList classEscaping, Attr attribute, PugModel model, PugTemplate template) { + final String name = attribute.getName(); boolean escaped = attribute.isEscaped(); String value = null; Object attributeValue = attribute.getValue(); + if(attributeValue instanceof ExpressionString){ + ExpressionString expressionString = (ExpressionString) attributeValue; + attributeValue = evaluateExpression(expressionString, model, template); + } + + if(skipAttribute(attributeValue)){ + return; + } + if("class".equals(name)) { - if (attributeValue instanceof String) { - value = (String) attributeValue; - } else if (attributeValue instanceof ExpressionString) { - Object expressionValue = evaluateExpression((ExpressionString) attributeValue, model,template.getExpressionHandler()); - - //List to String - if (expressionValue != null && expressionValue instanceof List){ - StringBuffer s = new StringBuffer(""); - List list = (List) expressionValue; - - boolean first = true; - for (Object o : list) { - if (!first) - s.append(" "); - s.append(o.toString()); - first = false; - } - value = s.toString(); - } - //Array to String - else if (expressionValue != null && expressionValue.getClass().isArray()) { - StringBuffer s = new StringBuffer(""); - boolean first = true; - if (expressionValue instanceof int[]) { - for (int o : (int[]) expressionValue) { - if (!first) - s.append(" "); - s.append(o); - first = false; - } - } else { - for (Object o : (Object[]) expressionValue) { - if (!first) - s.append(" "); - s.append(o.toString()); - first = false; - } - } - value = s.toString(); - }else if (expressionValue != null && expressionValue instanceof Map) { - Map map = (Map) expressionValue; - for (Map.Entry entry : map.entrySet()) { - if(entry.getValue() instanceof Boolean){ - if(((Boolean) entry.getValue()) == true){ - classes.add(entry.getKey()); - classEscaping.add(false); - } - } - } - }else if(expressionValue!=null && expressionValue instanceof Boolean){ - if((Boolean) expressionValue) { - value = expressionValue.toString(); - } - }else if(expressionValue!=null){ - value = expressionValue.toString(); - } - } - if(!StringUtils.isBlank(value)) { - classes.add(value); - classEscaping.add(escaped); - } + addClassValueToClassArray(classes, classEscaping, attributeValue, escaped); return; + } else if("style".equals(name)){ + value = renderStyleValue(attributeValue); } else { - if("style".equals(name)){ - if (attributeValue instanceof ExpressionString) { //isConstant - ExpressionString expressionString = (ExpressionString) attributeValue; - Object expressionValue = evaluateExpression(expressionString, model, template.getExpressionHandler()); - if (expressionValue == null) { - return; - } - attributeValue = style(expressionValue); - } else { - attributeValue = style(attributeValue); - } + value = renderNormalValue(template, attributeValue, name); + } + + if(escaped) + value = StringEscapeUtils.escapeHtml4(value); + + newAttributes.put(name,value); + } + + private Boolean skipAttribute(final Object attributeValue) { + Boolean skipAttribute = false; + if(attributeValue == null){ + skipAttribute = true; + } + if(attributeValue instanceof Boolean){ + if (!(Boolean) attributeValue) { + skipAttribute = true; } - if (attributeValue instanceof ExpressionString) { - ExpressionString expressionString = (ExpressionString) attributeValue; - Object expressionValue = evaluateExpression(expressionString, model, template.getExpressionHandler()); - if (expressionValue == null) { - return; - } + } + return skipAttribute; + } - if (expressionValue instanceof Boolean) { - Boolean booleanValue = (Boolean) expressionValue; - if (booleanValue) { - value = name; - } else { - return; - } - if (template.isTerse()) { - value = null; - } - } else if (expressionValue instanceof Instant) { - Instant instantValue = (Instant) expressionValue; - value = instantValue.toString(); - } else if ( - expressionValue.getClass().isArray() - || expressionValue instanceof Map - || expressionValue instanceof List - ) { - value = StringEscapeUtils.unescapeJava(gson.toJson(expressionValue)); - }else{ - value = expressionValue.toString(); + private String renderNormalValue(final PugTemplate template, final Object attributeValue, final String name) { + String value=null; + if (attributeValue instanceof Boolean) { + Boolean booleanValue = (Boolean) attributeValue; + if (booleanValue) { + value = name; + } + if (template.isTerse()) { + value = null; + } + } else if (attributeValue instanceof Instant) { + Instant instantValue = (Instant) attributeValue; + value = instantValue.toString(); + } else if (attributeValue != null && ( + attributeValue.getClass().isArray() + || attributeValue instanceof Map + || attributeValue instanceof List) + ) { + value = StringEscapeUtils.unescapeJava(gson.toJson(attributeValue)); + } else if (attributeValue instanceof String) { + value = (String) attributeValue; + } else if (attributeValue != null) { + value = attributeValue.toString(); + } + return value; + } + + private void addClassValueToClassArray(final ArrayList classes, final ArrayList classEscaping, final Object attributeValue, final boolean escaped) { + //List to String + String value = null; + if (attributeValue instanceof List){ + List list = (List) attributeValue; + for (Object o : list) { + classes.add(o.toString()); + classEscaping.add(escaped); + } + } + //Array to String + else if (attributeValue != null && attributeValue.getClass().isArray()) { + if (attributeValue instanceof int[]) { + for (int o : (int[]) attributeValue) { + classes.add(String.valueOf(o)); + classEscaping.add(escaped); } - }else if (attributeValue instanceof String) { - value = (String) attributeValue; - } else if (attributeValue instanceof Boolean) { - Boolean booleanValue = (Boolean) attributeValue; - if (booleanValue) { - value = name; - } else { - return; + } else { + for (Object o : (Object[]) attributeValue) { + classes.add(o.toString()); + classEscaping.add(escaped); } - if (template.isTerse()) { - value = null; + } + }else if (attributeValue instanceof Map) { + Map map = (Map) attributeValue; + for (Map.Entry entry : map.entrySet()) { + if(entry.getValue() instanceof Boolean){ + if(((Boolean) entry.getValue())){ + classes.add(entry.getKey()); + classEscaping.add(false); + } } } + }else if(attributeValue instanceof Boolean){ + if((Boolean) attributeValue) { + value = attributeValue.toString(); + } + }else if(attributeValue !=null){ + value = attributeValue.toString(); } - if(escaped) - value = StringEscapeUtils.escapeHtml4(value); - newAttributes.put(name,value); + if (!StringUtils.isBlank(value)) { + classes.add(value); + classEscaping.add(escaped); + } + } - private String style(Object value) { + private String renderStyleValue(Object value) { if(value instanceof Boolean && !(Boolean) value){ return ""; } if(value instanceof Map){ - String out = ""; + StringBuilder out = new StringBuilder(); Set> entries = ((Map) value).entrySet(); for (Map.Entry style : entries) { - out = out + style.getKey() + ":" + style.getValue()+";"; + out.append(style.getKey()).append(":").append(style.getValue()).append(";"); } - return out; + return out.toString(); }else{ return String.valueOf(value); } } - private Object evaluateExpression(ExpressionString attribute, PugModel model, ExpressionHandler expressionHandler) throws ExpressionException { + private Object evaluateExpression(ExpressionString attribute, PugModel model, PugTemplate pugTemplate) { String expression = attribute.getValue(); - Object result = expressionHandler.evaluateExpression(expression, model); - if (result instanceof ExpressionString) { - return evaluateExpression((ExpressionString) result, model, expressionHandler); + try { + return pugTemplate.getExpressionHandler().evaluateExpression(expression, model); + } catch (ExpressionException e) { + throw new PugCompilerException(this, pugTemplate.getTemplateLoader(), e); } - return result; } protected boolean isSelfClosingTag() { diff --git a/src/test/resources/issues/pug033.html b/src/test/resources/issues/pug033.html new file mode 100644 index 0000000..35c0d58 --- /dev/null +++ b/src/test/resources/issues/pug033.html @@ -0,0 +1,2 @@ +foo + diff --git a/src/test/resources/issues/pug033.jade b/src/test/resources/issues/pug033.jade new file mode 100644 index 0000000..693298f --- /dev/null +++ b/src/test/resources/issues/pug033.jade @@ -0,0 +1,9 @@ +doctype html +- var classes = ['foo', 'bar', 'baz'] +- var tof=4 +a.bang#main.bong(tuf="1" style={"color": 'red', "background": 'green'} taf=true class=classes class=['bing'] tif=3 tof=tof)&attributes({"tick":"tack","class":["blac","blic"],"bla":"1","bli":2,"blub":tof}) + +mixin link(href, name) + a.bang#main2.bong(tuf="1" style={"color": 'red', "background": 'green'} taf=true class=classes class=['bing'] tif=3 tof=tof)&attributes(attributes)= name + ++link('/foo','foo')(class="button" class="button2" mi="au")&attributes({"tick":"tack","class":["blac","blic"],"bla":"1","bli":2,"blub":tof}) \ No newline at end of file