docs: function (hash, val) {
  		var key,
  			value,
  			i,
  			element = this.element || {},
  			elemStyle = element.style,
  			nodeName = element.nodeName,
  			renderer = this.renderer,
  			symbolName = this.symbolName,
  			childNodes,
  			hasSetSymbolSize,
  			shadows = this.shadows,
  			skipAttr,
  			ret = this;
  
  		// single key-value pair
  		if (isString(hash) && defined(val)) {
  			key = hash;
  			hash = {};
  			hash[key] = val;
  		}
  
  		// used as a getter, val is undefined
  		if (isString(hash)) {
  			key = hash;
  			if (key === 'strokeWidth' || key === 'stroke-width') {
  				ret = this.strokeweight;
  			} else {
  				ret = this[key];
  			}
  
  		// setter
  		} else {
  			for (key in hash) {
  				value = hash[key];
  				skipAttr = false;
  
  				// prepare paths
  				// symbols
  				if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
  					// if one of the symbol size affecting parameters are changed,
  					// check all the others only once for each call to an element's
  					// .attr() method
  					if (!hasSetSymbolSize) {
  						this.symbolAttr(hash);
  
  						hasSetSymbolSize = true;
  					}
  
  					skipAttr = true;
  
  				} else if (key === 'd') {
  					value = value || [];
  					this.d = value.join(' '); // used in getter for animation
  
  					// convert paths
  					i = value.length;
  					var convertedPath = [];
  					while (i--) {
  
  						// Multiply by 10 to allow subpixel precision.
  						// Substracting half a pixel seems to make the coordinates
  						// align with SVG, but this hasn't been tested thoroughly
  						if (isNumber(value[i])) {
  							convertedPath[i] = mathRound(value[i] * 10) - 5;
  						} else if (value[i] === 'Z') { // close the path
  							convertedPath[i] = 'x';
  						} else {
  							convertedPath[i] = value[i];
  						}
  
  					}
  					value = convertedPath.join(' ') || 'x';
  					element.path = value;
  
  					// update shadows
  					if (shadows) {
  						i = shadows.length;
  						while (i--) {
  							shadows[i].path = value;
  						}
  					}
  					skipAttr = true;
  
  				// directly mapped to css
  				} else if (key === 'zIndex' || key === 'visibility') {
  
  					// issue 61 workaround
  					if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
  						element.gVis = value;
  						childNodes = element.childNodes;
  						i = childNodes.length;
  						while (i--) {
  							css(childNodes[i], { visibility: value });
  						}
  						if (value === VISIBLE) { // issue 74
  							value = null;
  						}
  					}
  
  					if (value) {
  						elemStyle[key] = value;
  					}
  
  
  
  					skipAttr = true;
  
  				// width and height
  				} else if (/^(width|height)$/.test(key)) {
  
  					this[key] = value; // used in getter
  
  					// clipping rectangle special
  					if (this.updateClipping) {
  						this[key] = value;
  						this.updateClipping();
  
  					} else {
  						// normal
  						elemStyle[key] = value;
  					}
  
  					skipAttr = true;
  
  				// x and y
  				} else if (/^(x|y)$/.test(key)) {
  
  					this[key] = value; // used in getter
  
  					if (element.tagName === 'SPAN') {
  						this.updateTransform();
  
  					} else {
  						elemStyle[{ x: 'left', y: 'top' }[key]] = value;
  					}
  
  				// class name
  				} else if (key === 'class') {
  					// IE8 Standards mode has problems retrieving the className
  					element.className = value;
  
  				// stroke
  				} else if (key === 'stroke') {
  
  					value = renderer.color(value, element, key);
  
  					key = 'strokecolor';
  
  				// stroke width
  				} else if (key === 'stroke-width' || key === 'strokeWidth') {
  					element.stroked = value ? true : false;
  					key = 'strokeweight';
  					this[key] = value; // used in getter, issue #113
  					if (isNumber(value)) {
  						value += PX;
  					}
  
  				// dashStyle
  				} else if (key === 'dashstyle') {
  					var strokeElem = element.getElementsByTagName('stroke')[0] ||
  						createElement(renderer.prepVML(['<stroke/>']), null, null, element);
  					strokeElem[key] = value || 'solid';
  					this.dashstyle = value; /* because changing stroke-width will change the dash length
  						and cause an epileptic effect */
  					skipAttr = true;
  
  				// fill
  				} else if (key === 'fill') {
  
  					if (nodeName === 'SPAN') { // text color
  						elemStyle.color = value;
  					} else {
  						element.filled = value !== NONE ? true : false;
  
  						value = renderer.color(value, element, key);
  
  						key = 'fillcolor';
  					}
  
  				// translation for animation
  				} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
  					if (key === 'align') {
  						key = 'textAlign';
  					}
  					this[key] = value;
  					this.updateTransform();
  
  					skipAttr = true;
  				} else if (key === 'text') { // text for rotated and non-rotated elements
  					this.bBox = null;
  					element.innerHTML = value;
  					skipAttr = true;
  				}
  
  
  				// let the shadow follow the main element
  				if (shadows && key === 'visibility') {
  					i = shadows.length;
  					while (i--) {
  						shadows[i].style[key] = value;
  					}
  				}
  
  
  
  				if (!skipAttr) {
  					if (docMode8) { // IE8 setAttribute bug
  						element[key] = value;
  					} else {
  						attr(element, key, value);
  					}
  				}
  			}
  		}
  		return ret;
  	}