function genXmlTextBody(slideObj) {
		// FIRST: Shapes without text, etc. may be sent here during build, but have no text to render so return an empty string
		if ( !slideObj.options.isTableCell && (typeof slideObj.text === 'undefined' || slideObj.text == null) ) return '';

		// Create options if needed
		if ( !slideObj.options ) slideObj.options = {};

		// Vars
		var arrTextObjects = [];
		var tagStart = ( slideObj.options.isTableCell ? '<a:txBody>'  : '<p:txBody>' );
		var tagClose = ( slideObj.options.isTableCell ? '</a:txBody>' : '</p:txBody>' );
		var strSlideXml = tagStart;

		// STEP 1: Modify slideObj to be consistent array of `{ text:'', options:{} }`
		/* CASES:
			addText( 'string' )
			addText( 'line1\n line2' )
			addText( ['barry','allen'] )
			addText( [{text'word1'}, {text:'word2'}] )
			addText( [{text'line1\n line2'}, {text:'end word'}] )
		*/
		// A: Handle string/number
		if ( typeof slideObj.text === 'string' || typeof slideObj.text === 'number' ) {
			slideObj.text = [ {text:slideObj.text.toString(), options:(slideObj.options || {})} ];
		}

		// STEP 2: Grab options, format line-breaks, etc.
		if ( Array.isArray(slideObj.text) ) {
			slideObj.text.forEach(function(obj,idx){
				// A: Set options
				obj.options = obj.options || slideObj.options || {};
				if ( idx == 0 && obj.options && !obj.options.bullet && slideObj.options.bullet ) obj.options.bullet = slideObj.options.bullet;

				// B: Cast to text-object and fix line-breaks (if needed)
				if ( typeof obj.text === 'string' || typeof obj.text === 'number' ) {
					obj.text = obj.text.toString().replace(/\r*\n/g, CRLF);
					// Plain strings like "hello \n world" need to have lineBreaks set to break as intended
					if ( obj.text.indexOf(CRLF) > -1 ) obj.options.breakLine = true;
				}

				// C: If text string has line-breaks, then create a separate text-object for each (much easier than dealing with split inside a loop below)
				if ( obj.text.split(CRLF).length > 0 ) {
					obj.text.toString().split(CRLF).forEach(function(line,idx){
						// Add line-breaks if not bullets/aligned (we add CRLF for those below in STEP 2)
						line += ( obj.options.breakLine && !obj.options.bullet && !obj.options.align ? CRLF : '' );
						arrTextObjects.push( {text:line, options:obj.options} );
					});
				}
				else {
					// NOTE: The replace used here is for non-textObjects (plain strings) eg:'hello\nworld'
					arrTextObjects.push( obj );
				}
			});
		}

		// STEP 3: Add bodyProperties
		{
			// A: 'bodyPr'
			strSlideXml += genXmlBodyProperties(slideObj.options);

			// B: 'lstStyle'
			// NOTE: Shape type 'LINE' has different text align needs (a lstStyle.lvl1pPr between bodyPr and p)
			// FIXME: LINE horiz-align doesnt work (text is always to the left inside line) (FYI: the PPT code diff is substantial!)
			if ( slideObj.options.h == 0 && slideObj.options.line && slideObj.options.align ) {
				strSlideXml += '<a:lstStyle><a:lvl1pPr algn="l"/></a:lstStyle>';
			}
			else if ( slideObj.type === 'placeholder' ) {
				strSlideXml += '<a:lstStyle>';
				strSlideXml += genXmlParagraphProperties(slideObj, true);
				strSlideXml += '</a:lstStyle>';
			}
			else {
				strSlideXml += '<a:lstStyle/>';
			}
		}

		// STEP 4: Loop over each text object and create paragraph props, text run, etc.
		arrTextObjects.forEach(function(textObj,idx){
			// Clear/Increment loop vars
			paragraphPropXml = '<a:pPr '+ (textObj.options.rtlMode ? ' rtl="1" ' : '');
			strXmlBullet = '', strXmlParaSpc = '';
			textObj.options.lineIdx = idx;

            // Inherit pPr-type options from parent shape's `options`
            textObj.options.align = textObj.options.align || slideObj.options.align;
            textObj.options.lineSpacing = textObj.options.lineSpacing || slideObj.options.lineSpacing;
            textObj.options.indentLevel = textObj.options.indentLevel || slideObj.options.indentLevel;
            textObj.options.paraSpaceBefore = textObj.options.paraSpaceBefore || slideObj.options.paraSpaceBefore;
            textObj.options.paraSpaceAfter = textObj.options.paraSpaceAfter || slideObj.options.paraSpaceAfter;

			textObj.options.lineIdx = idx;
			var paragraphPropXml = genXmlParagraphProperties(textObj, false);

			// B: Start paragraph if this is the first text obj, or if current textObj is about to be bulleted or aligned
			if ( idx == 0 ) {
				// Add paragraphProperties right after <p> before textrun(s) begin
				strSlideXml += '<a:p>' + paragraphPropXml;
			}
			else if ( idx > 0 && (typeof textObj.options.bullet !== 'undefined' || typeof textObj.options.align !== 'undefined') ) {
				strSlideXml += '</a:p><a:p>' + paragraphPropXml;
			}

			// C: Inherit any main options (color, fontSize, etc.)
			// We only pass the text.options to genXmlTextRun (not the Slide.options),
			// so the run building function cant just fallback to Slide.color, therefore, we need to do that here before passing options below.
			jQuery.each(slideObj.options, function(key,val){
				// NOTE: This loop will pick up unecessary keys (`x`, etc.), but it doesnt hurt anything
				if ( key != 'bullet' && !textObj.options[key] ) textObj.options[key] = val;
			});

			// D: Add formatted textrun
			strSlideXml += genXmlTextRun(textObj.options, textObj.text);
		});

		// STEP 5: Append 'endParaRPr' (when needed) and close current open paragraph
		// NOTE: (ISSUE#20/#193): Add 'endParaRPr' with font/size props or PPT default (Arial/18pt en-us) is used making row "too tall"/not honoring opts
		if ( slideObj.options.isTableCell && (slideObj.options.fontSize || slideObj.options.fontFace) ) {
			strSlideXml += '<a:endParaRPr lang="'+ ( slideObj.options.lang ? slideObj.options.lang : 'en-US' ) +'" '
				+ (slideObj.options.fontSize ? ' sz="'+ Math.round(slideObj.options.fontSize) +'00"' : '') + ' dirty="0">';
			if ( slideObj.options.fontFace ) {
				strSlideXml += '  <a:latin typeface="'+ slideObj.options.fontFace +'" charset="0" />';
				strSlideXml += '  <a:ea    typeface="'+ slideObj.options.fontFace +'" charset="0" />';
				strSlideXml += '  <a:cs    typeface="'+ slideObj.options.fontFace +'" charset="0" />';
			}
			strSlideXml += '</a:endParaRPr>';
		}
		else {
			strSlideXml += '<a:endParaRPr lang="'+ (slideObj.options.lang || 'en-US') +'" dirty="0"/>'; // NOTE: Added 20180101 to address PPT-2007 issues
		}
		strSlideXml += '</a:p>';

		// STEP 6: Close the textBody
		strSlideXml += tagClose;

		// LAST: Return XML
		return strSlideXml;
	}
DESC: Generate the XML for text and its options (bold, bullet, etc) including text runs (word-level formatting)
EX:
		<p:txBody>
			<a:bodyPr wrap="none" lIns="50800" tIns="50800" rIns="50800" bIns="50800" anchor="ctr">
			</a:bodyPr>
			<a:lstStyle/>
			<a:p>
			  <a:pPr marL="228600" indent="-228600"><a:buSzPct val="100000"/><a:buChar char="&#x2022;"/></a:pPr>
			  <a:r>
				<a:t>bullet 1 </a:t>
			  </a:r>
			  <a:r>
				<a:rPr>
				  <a:solidFill><a:srgbClr val="7B2CD6"/></a:solidFill>
				</a:rPr>
				<a:t>colored text</a:t>
			  </a:r>
			</a:p>
		  </p:txBody>
NOTES:
- PPT text lines [lines followed by line-breaks] are createing using <p>-aragraph's
- Bullets are a paragprah-level formatting device

@param slideObj (object) - slideObj -OR- table `cell` object
@returns XML string containing the param object's text and formatting