docs: public static void attributes(Data data, TagLibTag tag, Tag parent) throws TemplateException {
  	int type = tag.getAttributeType();
  	int start = data.srcCode.getPos();
  	// Tag with attribute names
  	if (type != TagLibTag.ATTRIBUTE_TYPE_NONAME) {
  	    try {
  		int min = tag.getMin();
  		int max = tag.getMax();
  		int count = 0;
  		ArrayList<String> args = new ArrayList<String>();
  		RefBoolean allowDefaultValue = new RefBooleanImpl(tag.getDefaultAttribute() != null);
  		while (data.srcCode.isValidIndex()) {
  		    data.srcCode.removeSpace();
  		    // if no more attributes break
  		    if (data.srcCode.isCurrent('/') || data.srcCode.isCurrent('>')) break;
  
  		    parent.addAttribute(attribute(data, tag, args, allowDefaultValue));
  		    count++;
  		}
  
  		// set default values
  		if (tag.hasDefaultValue()) {
  		    Map<String, TagLibTagAttr> hash = tag.getAttributes();
  		    Iterator<Entry<String, TagLibTagAttr>> it = hash.entrySet().iterator();
  		    Entry<String, TagLibTagAttr> e;
  		    TagLibTagAttr att;
  		    while (it.hasNext()) {
  			e = it.next();
  			att = e.getValue();
  			if (!parent.containsAttribute(att.getName()) && att.hasDefaultValue()) {
  
  			    Attribute attr = new Attribute(tag.getAttributeType() == TagLibTag.ATTRIBUTE_TYPE_DYNAMIC, att.getName(),
  				    data.factory.toExpression(data.factory.createLitString(Caster.toString(att.getDefaultValue(), null)), att.getType()), att.getType());
  			    attr.setDefaultAttribute(true);
  			    parent.addAttribute(attr);
  			}
  		    }
  		}
  
  		boolean hasAttributeCollection = args.contains("attributecollection");
  
  		// to less attributes
  		if (!hasAttributeCollection && min > count)
  		    throw createTemplateException(data.srcCode, "the tag " + tag.getFullName() + " must have at least " + min + " attributes", tag);
  
  		// too much attributes
  		if (!hasAttributeCollection && max > 0 && max < count)
  		    throw createTemplateException(data.srcCode, "the tag " + tag.getFullName() + " can have a maximum of " + max + " attributes", tag);
  
  		// not defined attributes
  		if (type == TagLibTag.ATTRIBUTE_TYPE_FIXED || type == TagLibTag.ATTRIBUTE_TYPE_MIXED) {
  		    // Map<String, TagLibTagAttr> hash = tag.getAttributes();
  		    Iterator<TagLibTagAttr> it = tag.getAttributes().values().iterator();
  
  		    while (it.hasNext()) {
  
  			TagLibTagAttr att = it.next();
  			if (att.isRequired() && !contains(args, att) && att.getDefaultValue() == null) {
  			    if (!hasAttributeCollection)
  				throw createTemplateException(data.srcCode, "attribute " + att.getName() + " is required for tag " + tag.getFullName(), tag);
  			    parent.addMissingAttribute(att);
  			}
  		    }
  		}
  	    }
  	    catch (TemplateException te) {
  		data.srcCode.setPos(start);
  		// if the tag supports a non name attribute try this
  		TagLibTagAttr sa = tag.getSingleAttr();
  		if (sa != null) attrNoName(parent, tag, data, sa);
  		else throw te;
  	    }
  	}
  	// tag without attributes name
  	else {
  	    attrNoName(parent, tag, data, null);
  	}
      }