docs: function _fnFeatureHtmlTable ( oSettings )
  		{
  			/* Check if scrolling is enabled or not - if not then leave the DOM unaltered */
  			if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY === "" )
  			{
  				return oSettings.nTable;
  			}
  			
  			/*
  			 * The HTML structure that we want to generate in this function is:
  			 *  div - nScroller
  			 *    div - nScrollHead
  			 *      div - nScrollHeadInner
  			 *        table - nScrollHeadTable
  			 *          thead - nThead
  			 *    div - nScrollBody
  			 *      table - oSettings.nTable
  			 *        thead - nTheadSize
  			 *        tbody - nTbody
  			 *    div - nScrollFoot
  			 *      div - nScrollFootInner
  			 *        table - nScrollFootTable
  			 *          tfoot - nTfoot
  			 */
  			var
  			 	nScroller = document.createElement('div'),
  			 	nScrollHead = document.createElement('div'),
  			 	nScrollHeadInner = document.createElement('div'),
  			 	nScrollBody = document.createElement('div'),
  			 	nScrollFoot = document.createElement('div'),
  			 	nScrollFootInner = document.createElement('div'),
  			 	nScrollHeadTable = oSettings.nTable.cloneNode(false),
  			 	nScrollFootTable = oSettings.nTable.cloneNode(false),
  				nThead = oSettings.nTable.getElementsByTagName('thead')[0],
  			 	nTfoot = oSettings.nTable.getElementsByTagName('tfoot').length === 0 ? null : 
  					oSettings.nTable.getElementsByTagName('tfoot')[0],
  				oClasses = oSettings.oClasses;
  			
  			nScrollHead.appendChild( nScrollHeadInner );
  			nScrollFoot.appendChild( nScrollFootInner );
  			nScrollBody.appendChild( oSettings.nTable );
  			nScroller.appendChild( nScrollHead );
  			nScroller.appendChild( nScrollBody );
  			nScrollHeadInner.appendChild( nScrollHeadTable );
  			nScrollHeadTable.appendChild( nThead );
  			if ( nTfoot !== null )
  			{
  				nScroller.appendChild( nScrollFoot );
  				nScrollFootInner.appendChild( nScrollFootTable );
  				nScrollFootTable.appendChild( nTfoot );
  			}
  			
  			nScroller.className = oClasses.sScrollWrapper;
  			nScrollHead.className = oClasses.sScrollHead;
  			nScrollHeadInner.className = oClasses.sScrollHeadInner;
  			nScrollBody.className = oClasses.sScrollBody;
  			nScrollFoot.className = oClasses.sScrollFoot;
  			nScrollFootInner.className = oClasses.sScrollFootInner;
  			
  			if ( oSettings.oScroll.bAutoCss )
  			{
  				nScrollHead.style.overflow = "hidden";
  				nScrollHead.style.position = "relative";
  				nScrollFoot.style.overflow = "hidden";
  				nScrollBody.style.overflow = "auto";
  			}
  			
  			nScrollHead.style.border = "0";
  			nScrollHead.style.width = "100%";
  			nScrollFoot.style.border = "0";
  			nScrollHeadInner.style.width = oSettings.oScroll.sXInner !== "" ?
  				oSettings.oScroll.sXInner : "100%"; /* will be overwritten */
  			
  			/* Modify attributes to respect the clones */
  			nScrollHeadTable.removeAttribute('id');
  			nScrollHeadTable.style.marginLeft = "0";
  			oSettings.nTable.style.marginLeft = "0";
  			if ( nTfoot !== null )
  			{
  				nScrollFootTable.removeAttribute('id');
  				nScrollFootTable.style.marginLeft = "0";
  			}
  			
  			/* Move caption elements from the body to the header, footer or leave where it is
  			 * depending on the configuration. Note that the DTD says there can be only one caption */
  			var nCaption = $(oSettings.nTable).children('caption');
  			if ( nCaption.length > 0 )
  			{
  				nCaption = nCaption[0];
  				if ( nCaption._captionSide === "top" )
  				{
  					nScrollHeadTable.appendChild( nCaption );
  				}
  				else if ( nCaption._captionSide === "bottom" && nTfoot )
  				{
  					nScrollFootTable.appendChild( nCaption );
  				}
  			}
  			
  			/*
  			 * Sizing
  			 */
  			/* When xscrolling add the width and a scroller to move the header with the body */
  			if ( oSettings.oScroll.sX !== "" )
  			{
  				nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX );
  				nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX );
  				
  				if ( nTfoot !== null )
  				{
  					nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX );	
  				}
  				
  				/* When the body is scrolled, then we also want to scroll the headers */
  				$(nScrollBody).scroll( function (e) {
  					nScrollHead.scrollLeft = this.scrollLeft;
  					
  					if ( nTfoot !== null )
  					{
  						nScrollFoot.scrollLeft = this.scrollLeft;
  					}
  				} );
  			}
  			
  			/* When yscrolling, add the height */
  			if ( oSettings.oScroll.sY !== "" )
  			{
  				nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY );
  			}
  			
  			/* Redraw - align columns across the tables */
  			oSettings.aoDrawCallback.push( {
  				"fn": _fnScrollDraw,
  				"sName": "scrolling"
  			} );
  			
  			/* Infinite scrolling event handlers */
  			if ( oSettings.oScroll.bInfinite )
  			{
  				$(nScrollBody).scroll( function() {
  					/* Use a blocker to stop scrolling from loading more data while other data is still loading */
  					if ( !oSettings.bDrawing && $(this).scrollTop() !== 0 )
  					{
  						/* Check if we should load the next data set */
  						if ( $(this).scrollTop() + $(this).height() > 
  							$(oSettings.nTable).height() - oSettings.oScroll.iLoadGap )
  						{
  							/* Only do the redraw if we have to - we might be at the end of the data */
  							if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() )
  							{
  								_fnPageChange( oSettings, 'next' );
  								_fnCalculateEnd( oSettings );
  								_fnDraw( oSettings );
  							}
  						}
  					}
  				} );
  			}
  			
  			oSettings.nScrollHead = nScrollHead;
  			oSettings.nScrollFoot = nScrollFoot;
  			
  			return nScroller;
  		}