docs: function (hash, val) {
  		var key,
  			value,
  			i,
  			child,
  			element = this.element,
  			nodeName = element.nodeName,
  			renderer = this.renderer,
  			skipAttr,
  			shadows = this.shadows,
  			htmlNode = this.htmlNode,
  			hasSetSymbolSize,
  			ret = this;
  
  		// single key-value pair
  		if (isString(hash) && defined(val)) {
  			key = hash;
  			hash = {};
  			hash[key] = val;
  		}
  
  		// used as a getter: first argument is a string, second is undefined
  		if (isString(hash)) {
  			key = hash;
  			if (nodeName === 'circle') {
  				key = { x: 'cx', y: 'cy' }[key] || key;
  			} else if (key === 'strokeWidth') {
  				key = 'stroke-width';
  			}
  			ret = attr(element, key) || this[key] || 0;
  
  			if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
  				ret = parseFloat(ret);
  			}
  
  		// setter
  		} else {
  
  			for (key in hash) {
  				skipAttr = false; // reset
  				value = hash[key];
  
  				// paths
  				if (key === 'd') {
  					if (value && value.join) { // join path
  						value = value.join(' ');
  					}
  					if (/(NaN| {2}|^$)/.test(value)) {
  						value = 'M 0 0';
  					}
  					this.d = value; // shortcut for animations
  
  				// update child tspans x values
  				} else if (key === 'x' && nodeName === 'text') {
  					for (i = 0; i < element.childNodes.length; i++) {
  						child = element.childNodes[i];
  						// if the x values are equal, the tspan represents a linebreak
  						if (attr(child, 'x') === attr(element, 'x')) {
  							//child.setAttribute('x', value);
  							attr(child, 'x', value);
  						}
  					}
  
  					if (this.rotation) {
  						attr(element, 'transform', 'rotate(' + this.rotation + ' ' + value + ' ' +
  							pInt(hash.y || attr(element, 'y')) + ')');
  					}
  
  				// apply gradients
  				} else if (key === 'fill') {
  					value = renderer.color(value, element, key);
  
  				// circle x and y
  				} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
  					key = { x: 'cx', y: 'cy' }[key] || key;
  
  				// translation and text rotation
  				} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
  					this[key] = value;
  					this.updateTransform();
  					skipAttr = true;
  
  				// apply opacity as subnode (required by legacy WebKit and Batik)
  				} else if (key === 'stroke') {
  					value = renderer.color(value, element, key);
  
  				// emulate VML's dashstyle implementation
  				} else if (key === 'dashstyle') {
  					key = 'stroke-dasharray';
  					value = value && value.toLowerCase();
  					if (value === 'solid') {
  						value = NONE;
  					} else if (value) {
  						value = value
  							.replace('shortdashdotdot', '3,1,1,1,1,1,')
  							.replace('shortdashdot', '3,1,1,1')
  							.replace('shortdot', '1,1,')
  							.replace('shortdash', '3,1,')
  							.replace('longdash', '8,3,')
  							.replace(/dot/g, '1,3,')
  							.replace('dash', '4,3,')
  							.replace(/,$/, '')
  							.split(','); // ending comma
  
  						i = value.length;
  						while (i--) {
  							value[i] = pInt(value[i]) * hash['stroke-width'];
  						}
  
  						value = value.join(',');
  					}
  
  				// special
  				} else if (key === 'isTracker') {
  					this[key] = value;
  
  				// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
  				// is unable to cast them. Test again with final IE9.
  				} else if (key === 'width') {
  					value = pInt(value);
  
  				// Text alignment
  				} else if (key === 'align') {
  					key = 'text-anchor';
  					value = { left: 'start', center: 'middle', right: 'end' }[value];
  
  
  				// Title requires a subnode, #431
  				} else if (key === 'title') {
  					var title = doc.createElementNS(SVG_NS, 'title');
  					title.appendChild(doc.createTextNode(value));
  					element.appendChild(title);
  				}
  
  
  
  				// jQuery animate changes case
  				if (key === 'strokeWidth') {
  					key = 'stroke-width';
  				}
  
  				// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
  				if (isWebKit && key === 'stroke-width' && value === 0) {
  					value = 0.000001;
  				}
  
  				// symbols
  				if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
  
  
  					if (!hasSetSymbolSize) {
  						this.symbolAttr(hash);
  						hasSetSymbolSize = true;
  					}
  					skipAttr = true;
  				}
  
  				// let the shadow follow the main element
  				if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
  					i = shadows.length;
  					while (i--) {
  						attr(shadows[i], key, value);
  					}
  				}
  
  				// validate heights
  				if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
  					value = 0;
  				}
  
  				if (key === 'text') {
  					// only one node allowed
  					this.textStr = value;
  					if (this.added) {
  						renderer.buildText(this);
  					}
  				} else if (!skipAttr) {
  					//element.setAttribute(key, value);
  					attr(element, key, value);
  				}
  
  				// Issue #38
  				if (htmlNode && (key === 'x' || key === 'y' ||
  						key === 'translateX' || key === 'translateY' || key === 'visibility')) {
  					var wrapper = this,
  						bBox,
  						arr = htmlNode.length ? htmlNode : [this],
  						length = arr.length,
  						itemWrapper,
  						j;
  					
  					for (j = 0; j < length; j++) {
  						itemWrapper = arr[j];
  						bBox = itemWrapper.getBBox();
  						htmlNode = itemWrapper.htmlNode; // reassign to child item
  						css(htmlNode, extend(wrapper.styles, {
  							left: (bBox.x + (wrapper.translateX || 0)) + PX,
  							top: (bBox.y + (wrapper.translateY || 0)) + PX
  						}));
  
  						if (key === 'visibility') {
  							css(htmlNode, {
  								visibility: value
  							});
  						}
  					}
  				}
  
  			}
  
  		}
  		return ret;
  	}