*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.21" SourceFile="vfpxworkbookxlsx.vcx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
*
DEFINE CLASS vfpxworkbookxlsx AS label 
 	*< CLASSDATA: Baseclass="label" Timestamp="" Scale="Pixels" Uniqueid="" />

	#INCLUDE "vfpxworkbookxlsx.h"
	*<DefinedPropArrayMethod>
		*m: addautofilter		&& Adds a filter to the column range
		*m: addclassdefinednumericformats		&& Adds pre-defined numeric cell formats
		*m: addcolumnfilter		&& Sets the specific filter for a column
		*m: addcustomdatetimeformat		&& Adds a new date or datetime format
		*m: addcustomnumericformat		&& Adds a new custom defined numeric format
		*m: addfilestozip		&& Adds the xml files for the xlsx file to a zip file
		*m: addgroupbycolumn		&& Adds a data group level by columns
		*m: addgroupbyrow		&& Adds a data group level by rows
		*m: addhyperlinkfile		&& Adds a hyperlink to the sheet for an external file
		*m: addhyperlinksheet		&& Adds a new hyperlink to the selected cell range with the target another cell range
		*m: addimage		&& Adds an image to a sheet
		*m: addindexcolor		&& Adds a new indexed color definition
		*m: addinlinefontobject		&& Adds an object for setting the inline formatting of cell text
		*m: addmrucolor		&& Adds a custom defined MRU color to the workbook
		*m: addnamedrange		&& Adds a name range to the workbook
		*m: addnumericformat		&& Adds a numeric format expression to be used
		*m: addsheet		&& Adds a new sheet to the workbook
		*m: addstringtoheap		&& Adds the string to the heap for parsing
		*m: addstringvalue		&& Adds a string value to the internal cursor
		*m: addstyleborders		&& Adds to the cell style a border definition
		*m: addstylecellformat		&& Adds the cell format Id to the format style (same as AddStyleNumericformat method)
		*m: addstylefill		&& Adds to the cell style a fill definition
		*m: addstylefont		&& Adds to the cell style a font definition
		*m: addstylehorizalignment		&& Adds to the cell style horizontal cell alignment
		*m: addstyleindent		&& Adds to the style indent definition
		*m: addstylenumericformat		&& Adds to the style definition numeric format
		*m: addstyleprotection		&& Adds the cell protection setting to the style
		*m: addstyletextrotation		&& Adds to the style definition text rotation
		*m: addstylevertalignment		&& Adds to the cell style vertical cell alignment
		*m: addstylewordwrap		&& Adds to the style definition word wrap
		*m: at		&& Fast string replacement for AT() function
		*m: cellformatpainter		&& Copies the formatting of a source cell to other cells
		*m: cellrefasciitoindex		&& Converts a 'AA444' cell reference to the row and column index values
		*m: checksheetname		&& Checks the sheet name for valid characters; returns a corrected string (invalid characters converted to underscore _)
		*m: clearautofilter		&& Clears the column filter for the sheet
		*m: clearcellvalidation		&& Clears/deletes the named range from the workbook
		*m: clearcellvalue		&& Clears the cell value
		*m: clearprintarea		&& Clears the selected sheet's print area
		*m: columnasciitoindex		&& Converts Excel Ascii column value to index value
		*m: columnindextoascii		&& Gets the column ASCII code for the column index
		*m: convertcentimeterstoemu		&& Converts centimeters to English Metric Units for image placement
		*m: convertcolortohex		&& Converts a color value (integer) to Hex representation
		*m: convertfiletostring		&& Reads a file into a String value
		*m: converthexstringtonumeric		&& ConvertHexStringToNumeric
		*m: convertpixelstocentimeters		&& Converts pixels to Centimeters for image placement
		*m: convertpixelstoexcelunits		&& Converts Pixel measurement to Excel measurement
		*m: convertrangetocolumnrowvalues		&& Converts a given range format to the column and row values
		*m: convertstringtofile		&& Converts a encoded string to file
		*m: copystyle		&& Copies the selected style to a new style Id
		*m: createexcelfile		&& Creates the Excel file from the components
		*m: createformatstyle		&& Creates a new format style definition
		*m: createinlineformattext		&& Creates an in-line text formatted definition
		*m: createworkbook		&& Creates a new workbook object
		*m: createworkbookex		&& Internal method for creating a workbook
		*m: createworkingcursors		&& Creates the working cursors for worksheet definitions
		*m: createworkingdirectories		&& Creates the working directories for the XML files
		*m: datetimeseparator_access
		*m: debugmethod		&& Used for debugging only
		*m: debug_assign
		*m: declaredll		&& Declares the required support Win32 API
		*m: deleteallworkbooks		&& Deletes all workbooks
		*m: deletecell		&& Deletes the selected cell
		*m: deletecolumn		&& Deletes the selected Column
		*m: deletehyperlink		&& Deletes the selected hyperlink
		*m: deleteimage		&& Deletes the image from the sheet
		*m: deleterow		&& Deletes the selected row
		*m: deletesheet		&& Deletes the sheet from the Workbook
		*m: deleteworkbook		&& Deletes the selected workbook
		*m: demo		&& Demo for the features of this class
		*m: derivesheetname		&& Derives the sheet name
		*m: destroyworkingcursors		&& Destroys the working cursors
		*m: documentation		&& Documentation of the methods and properties for this class
		*m: fastextract		&& Fast String Replacement for STREXTRACT() function
		*m: freezepanes		&& Freezes the panes of the sheet
		*m: getalphanumericsplit		&& Returns the position of the alpha and numeric split
		*m: getbordersrecord		&& Gets the border record; returns True if found, or False in not found
		*m: getcellalignment		&& Returns the cell alignment
		*m: getcellborders		&& Returns the cell border info
		*m: getcelldatatype		&& Returns the data type for the selected cell
		*m: getcellfill		&& Returns the fill info for the cell
		*m: getcellfont		&& Returns the cell font settings
		*m: getcellformula		&& Returns the cell formula expression
		*m: getcellindent		&& Returns the cell indentation
		*m: getcellnumberformat		&& Returns the format code for the selected cell
		*m: getcellnumberformattext		&& Returns the selected cell numeric format as text
		*m: getcellrecord		&& Positions to the xl_cells record; returns true if found, or false if not found
		*m: getcellstyle		&& Gets the cell style value
		*m: getcelltextrotation		&& Gets the cell text rotation value
		*m: getcellvalidation		&& Returns the cell validation settings
		*m: getcellvalue		&& Returns the value of the cell
		*m: getcellwordwrap		&& Returns the cell word wrap setting
		*m: getcellxfsid		&& Returns the Cell XfsId value
		*m: getcellxfsrecord		&& Positions the record pointer to xl_cellxfs record; returns true if found, or false if not found
		*m: getchecksum		&& Returns the checksum the string
		*m: getchildnodecount		&& Returns the number of child nodes
		*m: getchildnodeelement		&& Returns the selected child node
		*m: getcolumnhidden		&& Returns the column hidden setting
		*m: getcolumnincolrowpair		&& Returns the column (Ascii value) in a ColRow pair; i.e., returns A from A6 reference
		*m: getcolumnobject		&& Gets the column object definition
		*m: getcolumnwidth		&& Returns the column width
		*m: getcolwidthkeyexpr		&& Gets the key expression for c_colwidths
		*m: getcursorrecord		&& Gets the specified xl_cursor record; returns true if found, false if not found
		*m: getcustomnumericformat		&& Returns the format code for the specified custom numeric format
		*m: getcustompapersize		&& Gets the values for the custom paper size
		*m: getdisplaygridlines		&& Gets the display grid line setting for the sheet
		*m: getdwgpicdefinition		&& Gets the image drawing settings
		*m: getformatkeyexpr		&& Gets the key expression for t_format
		*m: getgridcolumnformat		&& Gets the grid column format for the cell formatting
		*m: getheaderfooterkeyexpr		&& Gets the key expression for the header footer table record
		*m: getimagedimensions		&& Gets the image height and width dimensions
		*m: getimagerelationshipid		&& Gets the image's relationship id
		*m: getinlinefontdefinition		&& Returns the inline formatting definition of cell text for each character group
		*m: getlastcolumninrow		&& Returns the last column index in the row
		*m: getlastrownumber		&& Returns the last row number in the sheet
		*m: getmaxcolumnnumber		&& Returns the max column number for a sheet
		*m: getnamedrange		&& Gets the named range
		*m: getnamedranges		&& Returns an object listing of all named ranges
		*m: getnextid		&& Gets the next sequential Id value
		*m: getnodeattributevalue		&& Returns the attribute value
		*m: getnodecount		&& Returns the number of nodes to be processed
		*m: getnodeelement		&& Gets the string content for the node element
		*m: getnodeelementvalue		&& Gets the value assigned to the node element
		*m: getnumberofsheets		&& Returns the number of sheets for the given workbook
		*m: getpapersize		&& Gets the paper size for the selected sheet
		*m: getprintarea		&& Gets the sheet print area settings
		*m: getprintorientation		&& Gets the print orientation for the sheet output
		*m: getrelationshipfilename		&& Gets the file name for the R:Id value in the relationships XML
		*m: getrelationtypetext		&& Gets the relation type text from the code
		*m: getrgbvalues		&& Gets the specified RGB color value
		*m: getrowheightkeyexpr		&& Gets the key expression for the c_rowheights
		*m: getrowhidden		&& Gets the row hidden setting
		*m: getrowincolrowpair		&& REturns the row (integer value) in a ColRow pair; i.e., return 6 from A6 reference
		*m: getrowmaxcolumn		&& Returns the selected row maximum column
		*m: getrowmaxfontsize		&& Returns the max fontsize used in the row
		*m: getsharedstringid		&& Gets the Shared Strings Id value for the cell text
		*m: getsheetindex		&& Gets the sheet internal index from the sheet name
		*m: getsheetname		&& Gets the sheet name
		*m: getsheetprotection		&& Returns the sheet protection settings
		*m: getsheetrecord		&& Positions the record in xl_sheets; returns true if record found, or false if not found
		*m: getsheetrowvalues		&& Returns the cell values for the given row
		*m: getsheetscale		&& Gets the sheet printing scale
		*m: getstringrecord		&& Positions the record pointer to the selected string record
		*m: getstringxml		&& Returns a converted string from XML to text
		*m: getstyleformatid		&& Gets the format style Id for the given numeric format, font format, and fill format
		*m: gettargetfromrelationships		&& Gets the target (file name) for the relationship id from the passed relationships file
		*m: getvalidation		&& Returns the validation definition
		*m: getvalidationlist		&& Returns a list of validations for the workbook/sheet
		*m: getworkbook		&& Gets the workbook Id from the name
		*m: getworkbookfilename		&& Gets the file name for the workbook index
		*m: getworkbookprotection		&& Gets the selected workbook protection
		*m: getworkbooksheets		&& Returns the workbook sheets
		*m: getxmlfirstsegment		&& Gets the first segment in a XML structure
		*m: getxmlheaderfootertext		&& Returns a XML formatted string for the header and footer text
		*m: getxmlnextsegment		&& Gets the next XML Segment in a XML structure
		*m: getxmlsegment		&& Gets the selected XML Segment
		*m: getxmlsegmentex		&& Returns the selected XML Segment
		*m: getxmlstring		&& Gets the string as an XML formatted string
		*m: initalizeidvalues		&& Initializes the internal Id values
		*m: insertcell		&& Inserts a new cell into the sheet
		*m: insertcolumn		&& Inserts a new column into the sheet
		*m: insertrow		&& Inserts a new row into the sheet
		*m: isarray		&& Determines if the parameter passed is an array
		*m: iscellformula		&& Returns True if cell contains a formula
		*m: iscellreferenceinvalid		&& Checks if the cell reference is invalid (outside of limits)
		*m: isformatstyledefined		&& Determines if the format is defined as a style
		*m: isvalidborderstyle		&& Validates the border style value
		*m: loaddrawingxml		&& Loads the drawing.xml into memory
		*m: loadsheetxml		&& Loads the sheet.xml into memory variable
		*m: loadstylesxml		&& Loads the styles.xml into memory
		*m: mergecells		&& Merges cells together
		*m: ondestroy		&& Called by the Destroy() event - user implementation
		*m: oninit		&& User code for Init() Event
		*m: onshowerrormessage		&& Event for displaying an error message to the user
		*m: onshowstatusmessage		&& Event for displaying a wait message to the user
		*m: opencreatedxlsxfile		&& Opens the XLSX file via Win32 API default program
		*m: openxlsxfileaszip		&& Opens the xlsx file and extracts the xml files to a temporary folder [override this method for an alternate way to extract the files to the folder]
		*m: openxlsxworkbook		&& Opens the passed Xlsx workbook and loads the internal cursors with the content
		*m: openxlsxworkbooksheet		&& Opens a selected worksheet in a XLXS workbook
		*m: parsestring		&& Parses a string based on a specified delimiter
		*m: readcellvalueformat		&& Reads the cell value and format for a sheet
		*m: readcommentxml		&& Reads the comment definitions for the sheet
		*m: readdrawingxml		&& Reads the drawing (image) xml for associated sheet
		*m: readexternalrefxml		&& Reads the External References file
		*m: readsharedstringsxml		&& Reads the sharedstrings.xml for opening workbooks
		*m: readsheetxml		&& Reads the sheet.xml for opening a workbook
		*m: readstylesxml		&& Reads the styles.xml file for opening a workbook
		*m: releasestringfromheap		&& Releases the string from the heap memory
		*m: removeworkingdirectories		&& Removes the working directories
		*m: renamesheet		&& Renames the selected sheet
		*m: resetcolumnwidth		&& Resets the column width to default
		*m: restorevfpsettings		&& Restores the VFP system settings
		*m: saveasutf8		&& Saves the file as a UTF-8
		*m: savegridtoworkbook		&& Saves the selected grid to a workbook
		*m: savegridtoworkbookex		&& Saves the passed grid to a workbook without adding the content to the internal cursors
		*m: savemultigridtoworkbookex		&& Same as SaveGridToWorkbookEx() method but handles multiple grids being passed
		*m: savetabletoworkbook		&& Saves the table passed (alias) as a workbook sheet
		*m: savetabletoworkbookex		&& Creates a new workbook from a table; direct write to XLSX file without loading internal cursors
		*m: savevfpsettings		&& Saves the VFP system settings
		*m: saveworkbook		&& Saves the selected workbook as an XLSX file
		*m: saveworkbookas		&& Saves the selected workbook with the new name
		*m: setcellalignment		&& Sets the cell horizontal and vertical alignments
		*m: setcellalignmentrange		&& Sets the cell alignment for a range of cells
		*m: setcellborder
		*m: setcellborderex
		*m: setcellborderrange		&& Sets the cell border for a range of cells
		*m: setcellfill		&& Sets the fill color for the cell
		*m: setcellfillrange		&& Sets the fill for a range of cells
		*m: setcellfont		&& Sets the selected cell font format
		*m: setcellfontrange		&& Sets the cell font format for a range of cells
		*m: setcellformula		&& Sets the cell formula expression
		*m: setcellindent		&& Sets the cell indentation value
		*m: setcellinlineformattext		&& Saves the in-line format definition to a cell value
		*m: setcellnumberdecimals		&& Sets the number of decimals to display
		*m: setcellnumberformat		&& Sets the cell number format
		*m: setcellnumberformatrange		&& Sets the number format for a range of cells
		*m: setcellstyle		&& Sets the cell style
		*m: setcellstylerange		&& Sets the cell style for a range of cells
		*m: setcelltextrotation		&& Sets the cell text rotation value
		*m: setcellvalidation		&& Adds a cell validation
		*m: setcellvalue		&& Sets the cell value
		*m: setcellwordwrap		&& Sets the cell word-wrapping value
		*m: setcellwordwraprange		&& Sets the word wrap for a range of cells
		*m: setcolumnbestfit		&& Set the column to best fit
		*m: setcolumnhidden		&& Sets the column hidden setting
		*m: setcolumnwidth		&& Sets the column width
		*m: setcolumnwidthrange		&& Sets the column width for a range of columns
		*m: setcustompapersize		&& Sets the paper size based on custom dimensions
		*m: setdefaultborder		&& Sets the default border style for the workbook
		*m: setdefaultfill		&& Sets the default fill for the workbook
		*m: setdefaultfont		&& Sets the default font for the workbook
		*m: setdisplaygridlines		&& Sets the sheet display grid lines setting
		*m: setheaderfootersetup		&& Sets the header and footer setup for the sheet
		*m: setheaderfootertext		&& Sets the header/footer text for the sheet
		*m: setlastid		&& Sets the last used Id Value for strings, borders, fills, fonts, and cellxfs
		*m: setpapersize		&& Sets the paper size for the selected sheet
		*m: setprintarea		&& Sets the print area for the sheet
		*m: setprintfittoheight		&& Number of vertical pages to fit on for printing
		*m: setprintfittowidth		&& Number of horizontal pages to fit on for printing
		*m: setprintorientation		&& Sets the printer orientation for sheet output
		*m: setrowheight		&& Sets the row height
		*m: setrowheightrange		&& Sets the row height for a range of rows
		*m: setrowhidden		&& Sets the row hidden setting
		*m: setrowmaxcolumn		&& Sets the value for the maximum cell column in a row
		*m: setsheetgroupsettings		&& Sets the group settings for a given sheet
		*m: setsheetmargins		&& Sets the sheet margin values
		*m: setsheetprintoptions		&& Sets the sheet print options
		*m: setsheetprotection		&& Sets the sheet protection on or off
		*m: setsheetscale		&& Sets the print scale; must be between 10 and 400; i.e. 10=10%, 100=100%, etc.
		*m: setsheetvisibility		&& Sets the sheet visibility
		*m: settabcolor		&& Sets the sheet tab color
		*m: setworkbookprotection		&& Sets the Workbook protection
		*m: testcode		&& Method to test code for debugging
		*m: ttoc3		&& Equlivalent of TTOC() function for pre VFP9 support
		*m: unfreezepanes		&& Removes the pane freeze from the sheet
		*m: ungroupbycolumn		&& Removes a data group level by columns
		*m: ungroupbyrow		&& Removes a data group level by rows
		*m: unmergedcells		&& Unmerges merged cells
		*m: writeasinlineformattedstring		&& Writes the string as formatted in-line
		*m: writeasinlinestring		&& Writes the value as an inlineStr value
		*m: writedirectcellformattedvalue		&& WriteDirectCellFormattedValue
		*m: writedirectcellvalue		&& Writes the cell value directly to the sheets xml
		*m: writedirectxmls		&& Writes the workbook XMLs for direct table/grid to workbook output
		*m: writedrawingdefinitionxml		&& Writes the drawing definition XML
		*m: writeimagerelationshipsxml		&& Writes the image relationship (.rels) xml file
		*m: writesheetrelationshipsxml		&& Writes the sheet relationships xml file
		*m: writesheetxmls		&& Writes the Sheet XML files
		*m: writestringsxml		&& Writes the sharedStrings XML
		*m: writestylesxml		&& Writes the styles.xml support file
		*m: writesupportxmls		&& Writes the [Content_Types].xml, .rels, core.xml, workbook.xml.rels and app.xml files
		*m: writeworkbookxml		&& Writes the workbook XML
		*p: autoheight		&& Boolean to indicate if the row heights are to be set by text font size of the largest fontsize in the row
		*p: autotrimsheetname		&& Indicates to auto-trim sheet name to max length ***Boolean
		*p: codepage		&& CodePage to use for the Strings cursor
		*p: companyname		&& Company Name
		*p: creatorname		&& Creator Name
		*p: datetimeseparator		&& Separator for a DATETIME value
		*p: debug		&& Sets debugging mode ***Boolean
		*p: declarewinapi		&& Flag to execute the DeclareDLL method in the Init() ***Boolean
		*p: defaultfont		&& Workbook Default Font
		*p: defaultfontsize		&& Default font size ***Integer
		*p: defaultpw		&& Default password for protection; encoded vfpxworkbookxlsx
		*p: defaultsheetname		&& Default sheet name (language specific)
		*p: errorlevelid		&& Error level Id ***Integer
		*p: excelxlsxrelease		&& Release version of Class ***Integer
		*p: nullvalue		&& Value to be assigned for field .NULL. values
		*p: oxdom		&& XML DOM Object
		*p: savecurrencyasnumeric		&& Flag to save a currency value as a numeric rather than currency in the spreadsheet; will convert the value to four decimal places ***Boolean
		*p: subject		&& Subject of Workbook (stored in properties)
		*p: title		&& Title of workbook (stored in properties)
		*p: truefalsevalue		&& The boolean value as displayed in Excel delimited by a pipe symbol
		*p: username		&& User Name
		*p: vfpsettings		&& VFP Settings set by core program; created as an object with properties in Init() event
		*a: globalid[1,2]		&& Global Id value for images
		*a: lastborderid[1,2]		&& Stores the last used border Id for a workbook
		*a: lastcellxfsid[1,2]		&& Stores the last used CellXfs Id for a workbook
		*a: lastdrawingrelsid[1,2]		&& LastDrawingRelsId
		*a: lastfillid[1,2]		&& Stores the last used fill Id for a workbook
		*a: lastfontid[1,2]		&& Stores the last used font Id for a workbook
		*a: lastindexcolorid[1,2]		&& Last used IndexedColor Id
		*a: lastmrucolorid[1,2]		&& Last used mruColor Id value
		*a: lastnumfmtid[1,2]		&& Last used NumFmt Id
		*a: lastsheetid[1,2]		&& Last used sheet Id
		*a: lastsheetrelsid[1,2]		&& LastSheetRelsId
		*a: laststringid[1,2]		&& Stores the last used string Id for a workbook
		*a: lastworkbookrelsid[1,2]		&& LastWorkbookRelsId
		*a: stringndx[1,0]		&& Returns the next string index value by workbook ***Integer
		*p: _memberdata		&& XML Metadata for customizable properties
	*</DefinedPropArrayMethod>

	PROTECTED destroy,init
	*<PropValue>
		autoheight = .F.
		AutoSize = .T.
		autotrimsheetname = .T.
		Caption = "clsVFPxWorkbookXLSX"
		codepage = 0
		companyname = VFPxWorkbookXLSX
		creatorname = VFPxWorkbookXLSX
		datetimeseparator = /
		debug = .F.
		declarewinapi = .T.
		defaultfont = Calibri
		defaultfontsize = 11
		defaultpw = F771
		defaultsheetname = Sheet
		errorlevelid = 0
		excelxlsxrelease = 38
		Height = 17
		Name = "vfpxworkbookxlsx"
		nullvalue = .NULL.
		oxdom = 
		savecurrencyasnumeric = .F.
		subject = 
		title = 
		truefalsevalue = Yes|No
		username = VFPxWorkbookXLSX
		vfpsettings = 
		Visible = .F.
		Width = 129
		_memberdata = <VFPData>
			<memberdata name="autoheight" type="property" display="AutoHeight"/>
			<memberdata name="autotrimsheetname" type="property" display="AutoTrimSheetName"/>
			<memberdata name="defaultfontsize" type="property" display="DefaultFontSize"/>
			<memberdata name="defaultpw" type="property" display="DefaultPW"/>
			<memberdata name="globalid" type="property" display="GlobalId"/>
			<memberdata name="lastdrawingrelsid" type="property" display="LastDrawingRelsId"/>
			<memberdata name="lastsheetrelsid" type="property" display="LastSheetRelsId"/>
			<memberdata name="lastworkbookrelsid" type="property" display="LastWorkbookRelsId"/>
			<memberdata name="nullvalue" type="property" display="NullValue"/>
			<memberdata name="vfpsettings" type="property" display="VFPSettings"/>
			<memberdata name="at" type="method" display="AT"/>
			<memberdata name="addautofilter" type="method" display="AddAutoFilter"/>
			<memberdata name="addcolumnfilter" type="method" display="AddColumnFilter"/>
			<memberdata name="addcustomdatetimeformat" type="method" display="AddCustomDateTimeFormat"/>
			<memberdata name="addfilestozip" type="method" display="AddFilesToZip"/>
			<memberdata name="addgroupbycolumn" type="method" display="AddGroupByColumn"/>
			<memberdata name="addgroupbyrow" type="method" display="AddGroupByRow"/>
			<memberdata name="addhyperlinkfile" type="method" display="AddHyperLinkFile"/>
			<memberdata name="addhyperlinksheet" type="method" display="AddHyperLinkSheet"/>
			<memberdata name="addimage" type="method" display="AddImage"/>
			<memberdata name="addstringtoheap" type="method" display="AddStringToHeap"/>
			<memberdata name="addstylecellformat" type="method" display="AddStyleCellFormat"/>
			<memberdata name="addstyleprotection" type="method" display="AddStyleProtection"/>
			<memberdata name="cellrefasciitoindex" type="method" display="CellRefAsciiToIndex"/>
			<memberdata name="checksheetname" type="method" display="CheckSheetName"/>
			<memberdata name="clearautofilter" type="method" display="ClearAutoFilter"/>
			<memberdata name="clearprintarea" type="method" display="ClearPrintArea"/>
			<memberdata name="convertcentimeterstoemu" type="method" display="ConvertCentimetersToEMU"/>
			<memberdata name="convertfiletostring" type="method" display="ConvertFileToString"/>
			<memberdata name="convertpixelstocentimeters" type="method" display="ConvertPixelsToCentimeters"/>
			<memberdata name="convertrangetocolumnrowvalues" type="method" display="ConvertRangeToColumnRowValues"/>
			<memberdata name="convertstringtofile" type="method" display="ConvertStringToFile"/>
			<memberdata name="copystyle" type="method" display="CopyStyle"/>
			<memberdata name="debugmethod" type="method" display="DebugMethod"/>
			<memberdata name="deletecell" type="method" display="DeleteCell"/>
			<memberdata name="deletecolumn" type="method" display="DeleteColumn"/>
			<memberdata name="deletehyperlink" type="method" display="DeleteHyperLink"/>
			<memberdata name="deleteimage" type="method" display="DeleteImage"/>
			<memberdata name="deleterow" type="method" display="DeleteRow"/>
			<memberdata name="fastextract" type="method" display="FastExtract"/>
			<memberdata name="getchecksum" type="method" display="GetCheckSum"/>
			<memberdata name="getcolumnhidden" type="method" display="GetColumnHidden"/>
			<memberdata name="getcolumnobject" type="method" display="GetColumnObject"/>
			<memberdata name="getdisplaygridlines" type="method" display="GetDisplayGridLines"/>
			<memberdata name="getdwgpicdefinition" type="method" display="GetDwgPicDefinition"/>
			<memberdata name="getgridcolumnformat" type="method" display="GetGridColumnFormat"/>
			<memberdata name="getimagedimensions" type="method" display="GetImageDimensions"/>
			<memberdata name="getimagerelationshipid" type="method" display="GetImageRelationshipId"/>
			<memberdata name="getnamedrange" type="method" display="GetNamedRange"/>
			<memberdata name="getnamedranges" type="method" display="GetNamedRanges"/>
			<memberdata name="getprintarea" type="method" display="GetPrintArea"/>
			<memberdata name="getrelationtypetext" type="method" display="GetRelationTypeText"/>
			<memberdata name="getrowhidden" type="method" display="GetRowHidden"/>
			<memberdata name="getrowmaxfontsize" type="method" display="GetRowMaxFontSize"/>
			<memberdata name="getsheetindex" type="method" display="GetSheetIndex"/>
			<memberdata name="getsheetprotection" type="method" display="GetSheetProtection"/>
			<memberdata name="getstringrecord" type="method" display="GetStringRecord"/>
			<memberdata name="getstyleformatid" type="method" display="GetStyleFormatId"/>
			<memberdata name="gettargetfromrelationships" type="method" display="GetTargetFromRelationships"/>
			<memberdata name="getworkbookprotection" type="method" display="GetWorkbookProtection"/>
			<memberdata name="getxmlfirstsegment" type="method" display="GetXMLFirstSegment"/>
			<memberdata name="getxmlnextsegment" type="method" display="GetXMLNextSegment"/>
			<memberdata name="getxmlsegment" type="method" display="GetXMLSegment"/>
			<memberdata name="getxmlsegmentex" type="method" display="GetXMLSegmentEx"/>
			<memberdata name="initalizeidvalues" type="method" display="InitalizeIdValues"/>
			<memberdata name="insertcell" type="method" display="InsertCell"/>
			<memberdata name="insertcolumn" type="method" display="InsertColumn"/>
			<memberdata name="insertrow" type="method" display="InsertRow"/>
			<memberdata name="isarray" type="method" display="IsArray"/>
			<memberdata name="isformatstyledefined" type="method" display="IsFormatStyleDefined"/>
			<memberdata name="loaddrawingxml" type="method" display="LoadDrawingXML"/>
			<memberdata name="loadsheetxml" type="method" display="LoadSheetXml"/>
			<memberdata name="loadstylesxml" type="method" display="LoadStylesXml"/>
			<memberdata name="openxlsxfileaszip" type="method" display="OpenXlsxFileAsZip"/>
			<memberdata name="openxlsxworkbooksheet" type="method" display="OpenXlsxWorkbookSheet"/>
			<memberdata name="readcommentxml" type="method" display="ReadCommentXML"/>
			<memberdata name="readdrawingxml" type="method" display="ReadDrawingXML"/>
			<memberdata name="releasestringfromheap" type="method" display="ReleaseStringFromHeap"/>
			<memberdata name="restorevfpsettings" type="method" display="RestoreVFPSettings"/>
			<memberdata name="saveasutf8" type="method" display="SaveAsUTF8"/>
			<memberdata name="savegridtoworkbookex" type="method" display="SaveGridToWorkbookEx"/>
			<memberdata name="savemultigridtoworkbookex" type="method" display="SaveMultiGridToWorkbookEx"/>
			<memberdata name="savetabletoworkbookex" type="method" display="SaveTableToWorkbookEx"/>
			<memberdata name="savevfpsettings" type="method" display="SaveVFPSettings"/>
			<memberdata name="setcolumnhidden" type="method" display="SetColumnHidden"/>
			<memberdata name="setdisplaygridlines" type="method" display="SetDisplayGridLines"/>
			<memberdata name="setprintarea" type="method" display="SetPrintArea"/>
			<memberdata name="setrowhidden" type="method" display="SetRowHidden"/>
			<memberdata name="setsheetgroupsettings" type="method" display="SetSheetGroupSettings"/>
			<memberdata name="setsheetprintoptions" type="method" display="SetSheetPrintOptions"/>
			<memberdata name="setsheetprotection" type="method" display="SetSheetProtection"/>
			<memberdata name="settabcolor" type="method" display="SetTabColor"/>
			<memberdata name="setworkbookprotection" type="method" display="SetWorkbookProtection"/>
			<memberdata name="testcode" type="method" display="TestCode"/>
			<memberdata name="ungroupbycolumn" type="method" display="UnGroupByColumn"/>
			<memberdata name="ungroupbyrow" type="method" display="UnGroupByRow"/>
			<memberdata name="writeasinlineformattedstring" type="method" display="WriteAsInLineFormattedString"/>
			<memberdata name="writeasinlinestring" type="method" display="WriteAsInLineString"/>
			<memberdata name="writedirectcellformattedvalue" type="method" display="WriteDirectCellFormattedValue"/>
			<memberdata name="writedirectcellvalue" type="method" display="WriteDirectCellValue"/>
			<memberdata name="writedirectxmls" type="method" display="WriteDirectXMLs"/>
			<memberdata name="writedrawingdefinitionxml" type="method" display="WriteDrawingDefinitionXML"/>
			<memberdata name="writeimagerelationshipsxml" type="method" display="WriteImageRelationshipsXML"/>
			<memberdata name="writesheetrelationshipsxml" type="method" display="WriteSheetRelationshipsXML"/>
		</VFPData>
	*</PropValue>
	
	PROCEDURE AddAutoFilter		&& Adds a filter to the column range
		LPARAMETERS tnWB, tnSheet, tnBegCol, tnEndCol
		DO CASE
			CASE PCOUNT() < 3
				RETURN False
		
			CASE PCOUNT() = 3
				tnEndCol = tnBegCol
		ENDCASE
		IF tnBegCol > tnEndCol
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_sheets", "sheet")
			REPLACE xl_sheets.addfilter   WITH True, ;
					xl_sheets.filtbegcol  WITH tnBegCol, ;
					xl_sheets.filtendcol  WITH tnEndCol IN xl_sheets
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROTECTED PROCEDURE AddClassDefinedNumericFormats		&& Adds pre-defined numeric cell formats
		LPARAMETERS tnWB, tnFormatId
		LOCAL llAdded, lcTextFormat, lcXMLFormat
		DO CASE
			CASE tnFormatId = CELL_FORMAT_CURRENCY_RED
				lcTextFormat = '"$"#,##0.00;[Red]("$"#,##0.00)'
				lcXMLFormat  = this.GetXMLString(lcTextFormat)
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, CELL_FORMAT_CURRENCY_RED, lcXMLFormat, lcTextFormat, False)
				llAdded = True
		
			CASE tnFormatId = CELL_FORMAT_ACC_CURR_POUNDS
				lcXMLFormat  = '_-[$&#163;-809]* #,##0.00_-;-[$&#163;-809]* #,##0.00_-;_-[$&#163;-809]* &quot;-&quot;??_-;_-@_-'
				lcTextFormat = '"£"#,##0.00;[Red]("£"#,##0.00)'
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, CELL_FORMAT_ACC_CURR_POUNDS, lcXMLFormat, lcTextFormat, False)
				llAdded = True
		
			CASE tnFormatId = CELL_FORMAT_ACC_CURR_EURO
				lcXMLFormat  = '_-[$&#8364;-2]* .00_-;-[$&#8364;-2]* .00_-;_-[$&#8364;-2]* &quot;-&quot;??_-;_-@_-'
				lcTextFormat = '"€"#,##0.00;[Red]("€"#,##0.00)'
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, CELL_FORMAT_ACC_CURR_EURO, lcXMLFormat, lcTextFormat, False)
				llAdded = True
		
			CASE tnFormatId = CELL_FORMAT_CURR_POUNDS_RED
				lcXMLFormat  = '[$&#163;-809]#,##0.00;[Red]\-[$&#163;-809]#,##0.00'
				lcTextFormat = '"£"#,##0.00;[Red]("£"#,##0.00)'
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, CELL_FORMAT_CURR_POUNDS_RED, lcXMLFormat, lcTextFormat, False)
				llAdded = True
		
			CASE tnFormatId = CELL_FORMAT_CURR_EURO_RED
				lcXMLFormat  = '[$&#8364;-2]\ #,##0.00_);[Red]\([$&#8364;-2]\ #,##0.00\)'
				lcTextFormat = '"€"#,##0.00;[Red]("€"#,##0.00)'
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, CELL_FORMAT_CURR_EURO_RED, lcXMLFormat, lcTextFormat, False)
				llAdded = True
		
			OTHERWISE
				llAdded = False
		ENDCASE
		RETURN llAdded
	ENDPROC

	PROCEDURE AddColumnFilter		&& Sets the specific filter for a column
		LPARAMETERS tnWB, tnSheet, tnCol, tcOperator, txFilterValue, tlAndOperator
		DO CASE
			CASE PCOUNT() < 5
				RETURN False
		
			CASE PCOUNT() = 5
				tlAndOperator = False
		ENDCASE
		INSERT INTO xl_filters (workbook, sheet, col, filtoper, filtvalue, andoperator) ;
			VALUES (tnWB, tnSheet, tnCol, tcOperator, TRANSFORM(txFilterValue), tlAndOperator)
		REPLACE xl_filters.andoperator WITH tlAndOperator FOR workbook = tnWB AND sheet = tnSheet AND col = tnCol IN xl_filters
		RETURN True
	ENDPROC

	PROCEDURE AddCustomDateTimeFormat		&& Adds a new date or datetime format
		LPARAMETERS tnWB, tcDateFormat
		LOCAL lcFormatCode, lnId
		IF PCOUNT() < 2
			RETURN 0
		ENDIF
		IF SEEK(BINTOC(tnWB)+PADR(tcDateFormat, 25, "X"), "xl_numFmts", "formatcode")
			RETURN xl_numFmts.id
		ELSE
			lcFormatCode = STRTRAN(tcDateFormat, " ", "\ ")
			IF ATC(CELL_FORMAT_LOCALE_CODE, lcFormatCode) = 0
				lcFormatCode = CELL_FORMAT_LOCALE_CODE + lcFormatCode
			ENDIF
			lnId = this.GetNextId(tnWB, 0, "xl_numfmts")
			INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
				VALUES (tnWB, lnId, this.GetXMLString(lcFormatCode), tcDateFormat, False)
			RETURN lnId
		ENDIF
	ENDPROC

	PROCEDURE AddCustomNumericFormat		&& Adds a new custom defined numeric format
		LPARAMETERS tnWB, tcPosFormat, tcNegFormat, tcZeroFormat, tcTextFormat, tlApplyDec
		LOCAL lnId, lcFormatCode, lnNdx, lcSetPoint, lcSeparator
		IF VARTYPE(tcPosFormat) != "C"
			RETURN 0
		ENDIF
		DO CASE
			CASE PCOUNT() = 1
				tcNegFormat  = ""
				tcZeroFormat = ""
				tcTextFormat = ""
				tlApplyDec   = False
		
			CASE PCOUNT() = 2
				tcZeroFormat = ""
				tcTextFormat = ""
				tlApplyDec   = False
		
			CASE PCOUNT() = 3
				tcTextFormat = ""
				tlApplyDec   = False
		
			CASE PCOUNT() = 4
				tlApplyDec = False
		ENDCASE
		IF VARTYPE(tcNegFormat) != "C"
			tcNegFormat = ""
		ENDIF
		IF VARTYPE(tcZeroFormat) != "C"
			tcZeroFormat = ""
		ENDIF
		IF VARTYPE(tcTextFormat) != "C"
			tcTextFormat = ""
		ENDIF
		IF OCCURS(";", tcPosFormat) > 0
			RETURN 0
		ENDIF
		IF OCCURS(";", tcNegFormat) > 0
			RETURN 0
		ENDIF
		IF OCCURS(";", tcZeroFormat) > 0
			RETURN 0
		ENDIF
		IF OCCURS(";", tcTextFormat) > 0
			RETURN 0
		ENDIF
		*-*	If not US standard for separator and decimal point, then change to US standard
		lcSetPoint  = SET("POINT")
		lcSeparator = SET("SEPARATOR")
		IF lcSetPoint != "." .OR. lcSeparator != ","
			tcPosFormat = CHRTRAN(tcPosFormat, lcSetPoint, TAB)
			tcPosFormat = CHRTRAN(tcPosFormat, lcSeparator, CR)
			tcPosFormat = CHRTRAN(tcPosFormat, TAB, ".")
			tcPosFormat = CHRTRAN(tcPosFormat, CR,  ",")
		
			tcNegFormat = CHRTRAN(tcNegFormat, lcSetPoint, TAB)
			tcNegFormat = CHRTRAN(tcNegFormat, lcSeparator, CR)
			tcNegFormat = CHRTRAN(tcNegFormat, TAB, ".")
			tcNegFormat = CHRTRAN(tcNegFormat, CR,  ",")
		
			tcZeroFormat = CHRTRAN(tcZeroFormat, lcSetPoint, TAB)
			tcZeroFormat = CHRTRAN(tcZeroFormat, lcSeparator, CR)
			tcZeroFormat = CHRTRAN(tcZeroFormat, TAB, ".")
			tcZeroFormat = CHRTRAN(tcZeroFormat, CR,  ",")
		
			tcTextFormat = CHRTRAN(tcTextFormat, lcSetPoint, TAB)
			tcTextFormat = CHRTRAN(tcTextFormat, lcSeparator, CR)
			tcTextFormat = CHRTRAN(tcTextFormat, TAB, ".")
			tcTextFormat = CHRTRAN(tcTextFormat, CR,  ",")
		ENDIF
		*-*	Build expression from parts
		lcFormatCode = tcPosFormat
		IF !EMPTY(tcNegFormat)
			lcFormatCode = lcFormatCode + ";" + tcNegFormat
		ENDIF
		IF !EMPTY(tcZeroFormat)
			lcFormatCode = lcFormatCode + ";" + tcZeroFormat
		ENDIF
		IF !EMPTY(tcTextFormat)
			lcFormatCode = lcFormatCode + ";" + tcTextFormat
		ENDIF
		lnId = this.GetNextId(tnWB, 0, "xl_numfmts")
		INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
			VALUES (tnWB, lnId, this.GetXMLString(lcFormatCode), lcFormatCode, tlApplyDec)
		RETURN lnId
	ENDPROC

	PROTECTED PROCEDURE AddFilesToZip		&& Adds the xml files for the xlsx file to a zip file
		LPARAMETERS tcTempPath, tcZipFile
		LOCAL llReturn, loShell, loFolder, loFile, lnCountBefore, loException
		TRY
			STRTOFILE(CHR(80)+CHR(75)+CHR(5)+CHR(6)+REPLICATE(CHR(0), 18), tcZipFile, 0)
		
			loShell  = CREATEOBJECT("shell.application")
			loFolder = loShell.NameSpace(tcTempPath).Items
			IF VAL(VERSION(4)) <= 9 .AND. (VAL(OS(3))<6 .OR. VAL(OS(3))=6 .AND. VAL(OS(4))<1)      && Fix for VFPA provided by Doug Hennig; VAL() fix provided by starfiresg1
				FOR EACH loFile IN loFolder
					loShell.NameSpace(tcZipFile).MoveHere(loFile, FOF_SILENT)
					apiSleep(100)
				ENDFOR
			ELSE
				FOR EACH loFile IN loFolder
					lnCountBefore = loShell.NameSpace(tcTempPath).Items.Count
					loShell.NameSpace(tcZipFile).MoveHere(loFile, FOF_SILENT)
					DO WHILE lnCountBefore = loShell.NameSpace(tcTempPath).Items.Count
						apiSleep(50)
					ENDDO
				ENDFOR
			ENDIF
			llReturn = True
		
		CATCH TO loException
			SET STEP ON
			this.ErrorLevelId = 12
			RAISEEVENT(this, "OnShowErrorMessage", 12, "CreateExcelFile - Failed to add contents to Zip file"+ CR + loException.Message)
			ERASE (tcZipFile)
			llReturn = False
		ENDTRY
		RETURN llReturn
	ENDPROC

	PROCEDURE AddGroupByColumn		&& Adds a data group level by columns
		LPARAMETERS tnWB, tnSheet, tnBegCol, tnEndCol
		LOCAL lnCol, lnLevel
		LOCAL ARRAY laLevel[1]
		IF PCOUNT() != 4 .OR. VARTYPE(tnBegCol) != "N" .OR. VARTYPE(tnEndCol) != "N"
			RETURN False
		ENDIF
		SELECT MAX(collevel) FROM xl_columns WHERE BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) .AND. col >= tnBegCol .AND. col <= tnEndCol INTO ARRAY laLevel
		lnLevel = NVL(laLevel[1], 0) + 1
		FOR lnCol=tnBegCol TO tnEndCol
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(lnCol), "xl_columns", "col")
				REPLACE xl_columns.collevel WITH lnLevel IN xl_columns
			ELSE
				INSERT INTO xl_columns (workbook, sheet, col, width, collevel, collapsed) VALUES (tnWB, tnSheet, lnCol, -1.0, lnLevel, False)
			ENDIF
		ENDFOR
		RETURN True
	ENDPROC

	PROCEDURE AddGroupByRow		&& Adds a data group level by rows
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnEndRow
		LOCAL lnRow, lnLevel
		LOCAL ARRAY laLevel[1]
		IF PCOUNT() != 4 .OR. VARTYPE(tnBegRow) != "N" .OR. VARTYPE(tnEndRow) != "N"
			RETURN False
		ENDIF
		SELECT MAX(rowlevel) FROM xl_rows WHERE BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) .AND. row >= tnBegRow .AND. row <= tnEndRow INTO ARRAY laLevel
		lnLevel = NVL(laLevel[1], 0) + 1
		FOR lnRow=tnBegRow TO tnEndRow
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(lnRow), "xl_rows", "row")
				REPLACE xl_rows.rowlevel WITH lnLevel IN xl_rows
			ELSE
				INSERT INTO xl_rows (workbook, sheet, row, height, rowlevel, collapsed, hidden) VALUES (tnWB, tnSheet, lnRow, -1.00, lnLevel, False, 0)
			ENDIF
		ENDFOR
		RETURN True
	ENDPROC

	PROCEDURE AddHyperLinkFile		&& Adds a hyperlink to the sheet for an external file
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tcTarget
		LOCAL ARRAY laFile[1]
		IF PCOUNT() = 7 .AND. !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnBegRow)+BINTOC(tnBegCol)+BINTOC(tnEndRow)+BINTOC(tnEndCol), "xl_hyperlinks", "hyperlink")
			IF ADIR(laFile, tcTarget) > 0
				lnRelId = this.GetNextId(tnWB, tnSheet, "xl_sheet_rels")
				INSERT INTO xl_hyperlinks (workbook, sheet, begrow, begcol, endrow, endcol, targettype, relid) ;
					VALUES (tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, HYPERLINK_TYPE_FILE, lnRelId)
		
				INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, external) ;
					VALUES (tnWB, tnSheet, RELS_SRC_SHEET, lnRelId, RELS_TYPE_HYPERLINK, tcTarget, True)
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE AddHyperLinkSheet		&& Adds a new hyperlink to the selected cell range with the target another cell range
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tnTgtSheet, tnTgtBegRow, tnTgtBegCol, tnTgtEndRow, tnTgtEndCol, tcDisplay
		IF PCOUNT() >= 11 .AND. !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnBegRow)+BINTOC(tnBegCol)+BINTOC(tnEndRow)+BINTOC(tnEndCol), "xl_hyperlinks", "hyperlink")
			IF PCOUNT() = 11 .OR. EMPTY(tcDisplay) .OR. VARTYPE(tcDisplay) != "C"
				tcDisplay = TRANSFORM(this.GetCellValue(tnWB, tnSheet, tnBegRow, tnBegCol))
			ENDIF
			INSERT INTO xl_hyperlinks (workbook, sheet, begrow, begcol, endrow, endcol, targettype, tgtsheet, tgtbegrow, tgtbegcol, tgtendrow, tgtendcol, display, relid) ;
				VALUES (tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, HYPERLINK_TYPE_SHEET, tnTgtSheet, tnTgtBegRow, tnTgtBegCol, tnTgtEndRow, tnTgtEndCol, tcDisplay, 0)
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE AddImage		&& Adds an image to a sheet
		LPARAMETERS tnWB, tnSheet, tcImageFile, tcAnchorType, tcImgMove, tnBegCol, tnBegColOff, tnBegRow, tnBegRowOff, tnEndCol, tnEndColOff, tnEndRow, tnEndRowOff
		LOCAL lnImgId, lnRelId, loDimens, lnGblId, lcFileExtn, lnCX, lnCY, lnImgWidth, lnImgHeight
		LOCAL ARRAY laFile[1]
		IF PCOUNT() < 3
			RETURN 0
		ENDIF
		IF !PEMSTATUS(_SCREEN, "System", 5) .OR. VARTYPE(_SCREEN.System) <> "O"
			RETURN 0
		ENDIF
		IF ADIR(laFile, tcImageFile) > 0
			loDimens = this.GetImageDimensions(tcImageFile)
			IF ISNULL(loDimens)
				RETURN 0
			ENDIF
			lnCX = this.ConvertPixelsToCentimeters(loDimens.Width, "W")
			lnCY = this.ConvertPixelsToCentimeters(loDimens.Height, "H")
			IF VARTYPE(tcAnchorType) = "L"
				tcAnchorType = IMAGE_ANCHOR_TYPE_ONE
			ENDIF
			IF VARTYPE(tcImgMove) = "L"
				tcImgMove = ""
			ENDIF
			IF VARTYPE(tnBegCol) = "L"
				tnBegCol = 1
			ENDIF
			IF VARTYPE(tnBegColOff) = "L"
				tnBegColOff = 0
			ENDIF
			IF VARTYPE(tnBegRow) = "L"
				tnBegRow = 1
			ENDIF
			IF VARTYPE(tnBegRowOff) = "L"
				tnBegRowOff = 0
			ENDIF
			IF VARTYPE(tnEndCol) = "L"
				tnEndCol = tnBegCol + 1
			ENDIF
			IF VARTYPE(tnEndColOff) = "L"
				tnEndColOff = lnCX
			ENDIF
			IF VARTYPE(tnEndRow) = "L"
				tnEndRow = tnBegRow + 1
			ENDIF
			IF VARTYPE(tnEndRowOff) = "L"
				tnEndRowOff = lnCY
			ENDIF
			tcAnchorType = NVL(tcAnchorType, IMAGE_ANCHOR_TYPE_ONE)
			tcImgMove    = NVL(tcImgMove, "")
			tnBegCol     = NVL(tnBegCol, 1)
			tnBegColOff  = NVL(tnBegColOff, 0)
			tnBegRow     = NVL(tnBegRow, 1)
			tnBegRowOff  = NVL(tnBegRowOff, 0)
			tnEndCol     = NVL(tnEndCol, tnBegCol+1)
			tnEndColOff  = NVL(tnEndColOff, 0)
			tnEndRow     = NVL(tnEndRow, tnBegRow+1)
			tnEndRowOff  = NVL(tnEndRowOff, 0)
			IF tcAnchorType = IMAGE_ANCHOR_TYPE_TWO .AND. EMPTY(tcImgMove)
				tcImgMove = IMAGE_ANCHOR_MOVE_ONE
			ENDIF
		
			lnImgHeight = this.ConvertCentimetersToEMU(lnCX)
			lnImgWidth  = this.ConvertCentimetersToEMU(lnCY)
		
			tnBegColOff = this.ConvertCentimetersToEMU(tnBegColOff)
			tnBegRowOff = this.ConvertCentimetersToEMU(tnBegRowOff)
			tnEndColOff = this.ConvertCentimetersToEMU(tnEndColOff)
			tnEndRowOff = this.ConvertCentimetersToEMU(tnEndRowOff)
		
			lnImgId = this.GetNextId(tnWB, tnSheet, "xl_drawing_rels")
			lnGblId = this.GetNextId(tnWB, tnSheet, "global")
		
			INSERT INTO xl_images (workbook, sheet, relid, anchor, imgmove, begcol, begcoloff, begrow, begrowoff, endcol, endcoloff, endrow, endrowoff, cx, cy) ;
				VALUES (tnWB, tnSheet, lnImgId, tcAnchorType, tcImgMove, tnBegCol-1, tnBegColOff, tnBegRow-1, tnBegRowOff, tnEndCol-1, tnEndColOff, ;
						tnEndRow-1, tnEndRowOff, lnImgHeight, lnImgWidth)
		
			INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, external, globalid, filecontent) ;
				VALUES (tnWB, tnSheet, RELS_SRC_DRAWING, lnImgId, RELS_TYPE_IMAGE, JUSTFNAME(tcImageFile), False, lnGblId, this.ConvertFileToString(tcImageFile, 13))
		
			IF !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+RELS_TYPE_DRAWING, "xl_relationships", "reltype")
				lnRelId = this.GetNextId(tnWB, tnSheet, "xl_sheet_rels")
				INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, external) ;
					VALUES (tnWB, tnSheet, RELS_SRC_SHEET, lnRelId, RELS_TYPE_DRAWING, "../drawings/drawing" + TRANSFORM(tnSheet) + ".xml", False)
			ENDIF
		
			lcFileExtn = LOWER(JUSTEXT(tcImageFile))
			IF !SEEK(BINTOC(tnWB)+lcFileExtn, "xl_contenttypes", "extension")
				INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (tnWB, lcFileExtn, "image/" + lcFileExtn)
			ENDIF
		ELSE
			RETURN 0
		ENDIF
		RETURN lnImgId
	ENDPROC

	PROCEDURE AddIndexColor		&& Adds a new indexed color definition
		LPARAMETERS tnWB, tnRGBColor
		LOCAL lnIndexId, lcHexColor
		IF PCOUNT() != 2 .OR. VARTYPE(tnRGBColor) != "N"
			RETURN 0
		ENDIF
		lcHexColor = this.ConvertColorToHex(tnRGBColor)
		IF SEEK(BINTOC(tnWB)+lcHexColor, "xl_ndxcolors", "rgbcolor")
			lnIndexId = xl_ndxcolors.indexid
		ELSE
			lnIndexId = this.GetNextId(tnWB, 0, "xl_ndxcolors")
			INSERT INTO xl_ndxcolors (workbook, indexid, rgbcolor) VALUES (tnWB, lnIndexId, lcHexColor)
		ENDIF
		RETURN lnIndexId
	ENDPROC

	PROCEDURE AddInLineFontObject		&& Adds an object for setting the inline formatting of cell text
		LPARAMETERS toInline, tnBeg, tnLen, tcFontName, tnFontSize, tnFontColor, tlFontBold, tlFontItalic, tcULine, tlStrkThru, tlSubscript, tlSuperscript
		LOCAL loCharacter
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF VARTYPE(toInline) != "O"
			RETURN .NULL.
		ENDIF
		DO CASE
			CASE PCOUNT() = 4
				tnFontSize    = 10
				tnFontColor   = RGB(0,0,0)
				tlFontBold    = False
				tlFontItalic  = False
				tcULine       = UNDERLINE_NONE
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 5
				tnFontColor   = RGB(0,0,0)
				tlFontBold    = False
				tlFontItalic  = False
				tcULine       = UNDERLINE_NONE
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 6
				tlFontBold    = False
				tlFontItalic  = False
				tcULine       = UNDERLINE_NONE
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 7
				tlFontItalic  = False
				tcULine       = UNDERLINE_NONE
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 8
				tcULine       = UNDERLINE_NONE
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 9
				tlStrkThru    = False
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 10
				tlSubscript   = False
				tlSuperscript = False
		
			CASE PCOUNT() = 11
				tlSuperscript = False
		ENDCASE
		loCharacter = CREATEOBJECT("Empty")
		ADDPROPERTY(loCharacter, "BegPos", tnBeg)
		ADDPROPERTY(loCharacter, "Length", tnLen)
		ADDPROPERTY(loCharacter, "FontName", tcFontName)
		ADDPROPERTY(loCharacter, "FontSize", tnFontSize)
		ADDPROPERTY(loCharacter, "FontBold", tlFontBold)
		ADDPROPERTY(loCharacter, "FontItalic", tlFontItalic)
		ADDPROPERTY(loCharacter, "FontColor", tnFontColor)
		ADDPROPERTY(loCharacter, "Underline", tcULine)
		ADDPROPERTY(loCharacter, "StrikeThru", tlStrkThru)
		ADDPROPERTY(loCharacter, "SubScript", tlSubscript)
		ADDPROPERTY(loCharacter, "SuperScript", tlSuperscript)
		
		toInline.Count = toInline.Count + 1
		DIMENSION toInline.Characters[toInline.Count]
		toInline.Characters[toInline.Count] = loCharacter
		
		RETURN toInline
	ENDPROC

	PROCEDURE AddMruColor		&& Adds a custom defined MRU color to the workbook
		LPARAMETERS tnWB, tnRGBColor
		LOCAL lnIndexId, lcHexColor
		IF PCOUNT() != 2 .OR. VARTYPE(tnRGBColor) != "N"
			RETURN 0
		ENDIF
		lcHexColor = this.ConvertColorToHex(tnRGBColor)
		IF SEEK(BINTOC(tnWB)+lcHexColor, "xl_mrucolors", "rgbcolor")
			lnIndexId = xl_mrucolors.indexid
		ELSE
			lnIndexId = this.GetNextId(tnWB, 0, "xl_mrucolors")
			INSERT INTO xl_mrucolors (workbook, indexid, rgbcolor) VALUES (tnWB, lnIndexId, lcHexColor)
		ENDIF
		RETURN lnIndexId
	ENDPROC

	PROCEDURE AddNamedRange		&& Adds a name range to the workbook
		LPARAMETERS tnWB, tnSheet, tcName, tnScope, tcComment, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		LOCAL lcRangeName
		IF PCOUNT() < 8
			RETURN ""
		ENDIF
		IF VARTYPE(tcName) != "C" .OR. LEN(tcName) > 50
			RETURN ""
		ENDIF
		IF VARTYPE(tnScope) != "N"
			tnScope = 0
		ENDIF
		IF VARTYPE(tcComment) != "C"
			tcComment = ""
		ENDIF
		IF LEN(tcComment) > 254
			RETURN ""
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN ""
		ENDIF
		lcRangeName = CHRTRAN(ALLTRIM(tcName), " !@#$%^&*()+={}[]|<>,.'?/" + CHR(34), REPLICATE("_", 26))
		IF !ISALPHA(lcRangeName) .AND. !INLIST(LEFT(lcRangeName, 1), "_", "\")
			lcRangeName = "_" + lcRangeName
		ENDIF
		IF INLIST(UPPER(lcRangeName), "_FILTERDATABASE", "CRITERIA", "EXTRACT", "PRINT_AREA", "PRINT_TITLE")
			lcRangeName = "_xlnm." + lcRangeName
		ENDIF
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			IF SEEK(BINTOC(tnWB)+PADL(lcRangeName, 50, " "), "xl_namerange", "wbrname")
				UPDATE xl_namerange ;
					SET begrow = tnBegRow, ;
						begcol = tnBegCol, ;
						endrow = tnEndRow, ;
						endcol = tnEndCol ;
					WHERE workbook = tnWB ;
						AND sheet = tnSheet ;
						AND rname = PADL(lcRangeName, 50, " ")
			ELSE
				INSERT INTO xl_namerange (workbook, sheet, rname, scope, comment, begrow, begcol, endrow, endcol) ;
					VALUES (tnWB, tnSheet, lcRangeName, tnScope, tcComment, tnBegRow, tnBegCol, tnEndRow, tnEndCol)
			ENDIF
		ELSE
			RETURN ""
		ENDIF
		RETURN lcRangeName
	ENDPROC

	PROCEDURE AddNumericFormat		&& Adds a numeric format expression to be used
		LPARAMETERS tnWB, tcFormatCode
		LOCAL lcPosCode, lcNegCode, lcZerCode
		IF PCOUNT() < 2
			RETURN 0
		ENDIF
		IF VARTYPE(tnWB) != "N" .AND. tnWB > 0
			RETURN 0
		ENDIF
		IF VARTYPE(tcFormatCode) != "C"
			RETURN 0
		ENDIF
		lcPosCode = this.ParseString(tcFormatCode, 1, ";")
		lcNegCode = this.ParseString(tcFormatCode, 2, ";")
		lcZerCode = this.ParseString(tcFormatCode, 3, ";")
		RETURN this.AddCustomNumericFormat(tnWB, lcPosCode, lcNegCode, lcZerCode)
	ENDPROC

	PROCEDURE AddSheet		&& Adds a new sheet to the workbook
		LPARAMETERS tnWB, tcSheetName, tnState
		LOCAL lnShId, lnRelId
		lnShId = 0
		IF PCOUNT() > 0 .AND. SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			lnShId = this.GetNextId(tnWB, 0, "xl_sheets")
			DO CASE
				CASE PCOUNT() = 0
				
				CASE PCOUNT() = 1
					tcSheetName = "Sheet" + TRANSFORM(lnShId + 1)        && Bug fix identified by Dan Lauer
					tnState = VISIBLE_SHEET_STATE
				
				CASE PCOUNT() = 2
					IF VARTYPE(tcSheetName) != "C"
						tcSheetName = "Sheet" + TRANSFORM(lnShId + 1)
					ENDIF
					tnState = VISIBLE_SHEET_STATE
		
				OTHERWISE
					IF VARTYPE(tcSheetName) != "C"
						tcSheetName = "Sheet" + TRANSFORM(lnShId + 1)
					ENDIF
					IF VARTYPE(tnState) != "N"
						tnState = VISIBLE_SHEET_STATE
					ENDIF
			ENDCASE
			IF EMPTY(tcSheetName)
				tcSheetName = "Sheet" + TRANSFORM(lnShId + 1)
			ELSE
				tcSheetName = this.CheckSheetName(tcSheetName)
				IF ISNULL(tcSheetName)
					this.SetLastId(tnWB, 0, lnShId-1, "xl_sheets")
					RETURN 0
				ENDIF
			ENDIF
			IF SEEK(BINTOC(tnWB)+UPPER(PADR(tcSheetName, LEN(xl_sheets.shname))), "xl_sheets", "shname")    && Change recommendation by Doug Hennig
				lnShId = 0
			ELSE
				INSERT INTO xl_sheets (workbook, sheet, shname, state, mleft, mright, mtop, mbot, mheader, mfooter, shdeleted, xsplit, ysplit, prnorient, ;
									  papersize, paperwidth, paperheight, scale, fittowidth, fittoheight, autofilter, deletecol, deleterow, formatcell, formatcol, ;
									  formatrow, insertcol, insertrow, inserthyper, objects, pivottbl, scenarios, sellocked, selunlocked, sort, protection, gridlines) ;
					VALUES (tnWB, lnShId, tcSheetName, tnState, 0.75, 0.75, 0.75, 0.75, 0.3, 0.3, False, 0, 0, PORTRAIT_PRINT_ORIENTATION, 0, 0, 0, 100, 0, 0, ;
							True, True, True, True, True, True, True, True, True, True, True, True, False, False, True, False, 1)
		
				lnRelId = this.GetNextId(tnWB, 0, "xl_workbook_rels")
				INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
					VALUES (tnWB, lnShId, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_WORKSHEET, "worksheets/sheet" + TRANSFORM(lnShId) + ".xml")
			ENDIF
		ENDIF
		RETURN lnShId
	ENDPROC

	PROTECTED PROCEDURE AddStringToHeap		&& Adds the string to the heap for parsing
		LPARAMETERS tcString
		LOCAL loString
		loString = CREATEOBJECT("Empty")
		ADDPROPERTY(loString, "BaseAdr", 0)
		ADDPROPERTY(loString, "LastAdr", 0)
		ADDPROPERTY(loString, "Length", LEN(tcString))
		ADDPROPERTY(loString, "XMLDelimiter", "")
		ADDPROPERTY(loString, "BegDelimiter", "")
		ADDPROPERTY(loString, "EndDelimiter", "")
		ADDPROPERTY(loString, "TextString", tcString)
		loString.BaseAdr = apiHeapAlloc(apiGetProcessHeap(), 0, loString.Length)
		SYS(2600, loString.BaseAdr, loString.Length, tcString)
		RETURN loString
	ENDPROC

	PROTECTED PROCEDURE AddStringValue		&& Adds a string value to the internal cursor
		LPARAMETERS tnWB, tcString, tlInLine
		LOCAL lcStringXml, lnStringId, llPresrvSp, lcCheckSum
		IF tlInLine
			llPresrvSp  = IIF(LEFT(tcString, 1) = " ", True, False)
			lcStringXml = this.GetXMLString(tcString)
			lnStringId  = this.GetNextId(tnWB, 0, "xl_strings")
			lcCheckSum  = this.GetCheckSum(tcString)
			INSERT INTO xl_strings (id, workbook, checksum, stringxml, stringval, presvspace, formatted) ;
				VALUES (lnStringId, tnWB, lcCheckSum, lcStringXml, tcString, llPresrvSp, True)
		ELSE
			IF this.GetStringRecord(tnWB, tcString) .AND. xl_strings.formatted = False
				lnStringId = xl_strings.id
			ELSE
				llPresrvSp  = IIF(LEFT(tcString, 1) = " " .OR. RIGHT(tcString, 1) = " ", True, False)
				lcStringXml = this.GetXMLString(tcString)
				lnStringId  = this.GetNextId(tnWB, 0, "xl_strings")
				lcCheckSum  = this.GetCheckSum(tcString)
				INSERT INTO xl_strings (id, workbook, checksum, stringxml, stringval, presvspace, formatted) ;
					VALUES (lnStringId, tnWB, lcCheckSum, lcStringXml, tcString, llPresrvSp, False)
			ENDIF
		ENDIF
		RETURN lnStringId
	ENDPROC

	PROCEDURE AddStyleBorders		&& Adds to the cell style a border definition
		LPARAMETERS tnWB, tnCellXfsId, tnBorders, tcStyle, tnColor
		LOCAL lnDiagDn, lnDiagUp, lcLStyle, lnLColor, lcRStyle, lnRColor, lcTStyle, lnTColor, lcBStyle, lnBColor
		LOCAL lcDStyle, lnDColor, lnBorderId
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tnBorders) != 'N' .OR. EMPTY(tnBorders) .OR. !BETWEEN(tnBorders, 0, 63)
				tnBorders = 0
			ENDIF
			IF !this.IsValidBorderStyle(tcStyle)
				tcStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnColor) != 'N' .OR. !BETWEEN(tnColor, 0, RGB(255,255,255)) .OR. EMPTY(tnColor)
				tnColor = RGB(0, 0, 0)
			ENDIF
			STORE 0 TO lnDiagDn, lnDiagUp
			STORE RGB(0,0,0) TO lnLColor, lnRColor, lnTColor, lnBColor, lnDColor
			STORE BORDER_STYLE_NONE TO lcLStyle, lcRStyle, lcTStyle, lcBStyle, lcDStyle
			IF BITTEST(tnBorders, 0)
				lcLStyle = tcStyle
				lnLColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 1)
				lcRStyle = tcStyle
				lnRColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 2)
				lcTStyle = tcStyle
				lnTColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 3)
				lcBStyle = tcStyle
				lnBColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 4)
				lcDStyle = tcStyle
				lnDColor = tnColor
				lnDiagDn = 1
			ENDIF
			IF BITTEST(tnBorders, 5)
				lcDStyle = tcStyle
				lnDColor = tnColor
				lnDiagUp = 1
			ENDIF
			IF this.GetBordersRecord(tnWB, lcLStyle, lnLColor, lcRStyle, lnRColor, lcTStyle, lnTColor, lcBStyle, lnBColor, lcDStyle, lnDColor, lnDiagDn, lnDiagUp)
				lnBorderId = xl_borders.id
			ELSE
				lnBorderId = this.GetNextId(tnWB, 0, 'xl_borders')
				INSERT INTO xl_borders (workbook, id, lstyle, lcolor, rstyle, rcolor, tstyle, tcolor, bstyle, bcolor, dstyle, dcolor, diagdn, diagup) ;
					VALUES (tnWB, lnBorderId, lcLStyle, lnLColor, lcRStyle, lnRColor, lcTStyle, lnTColor, lcBStyle, lnBColor, lcDStyle, lnDColor, lnDiagDn, lnDiagUp)
			ENDIF	
		*-*	Set the border id to the style definition
			REPLACE xl_cellxfs.borderid    WITH lnBorderId, ;
					xl_cellxfs.applyborder WITH 1 IN xl_cellxfs
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleCellFormat		&& Adds the cell format Id to the format style (same as AddStyleNumericformat method)
		LPARAMETERS tnWB, tnCellXfsId, tnNumFmtId
		RETURN this.AddStyleNumericFormat(tnWB, tnCellXfsId, tnNumFmtId)
	ENDPROC

	PROCEDURE AddStyleFill		&& Adds to the cell style a fill definition
		LPARAMETERS tnWB, tnCellXfsId, tnFColor, tnBColor, tcPatternType
		LOCAL lnFillId, lnFIndex, lnBIndex
		DO CASE
			CASE PCOUNT() < 3
				RETURN False
		
			CASE PCOUNT() = 3
				tnBColor = tnFColor
				tcPatternType = FILL_STYLE_SOLID
		
			CASE PCOUNT() = 4
				tcPatternType = FILL_STYLE_SOLID
		ENDCASE
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tnFColor) = 'N' .AND. BETWEEN(tnFColor, 0, RGB(255, 255, 255))
				tnFColor = FLOOR(tnFColor)
				lnFIndex = 0
			ELSE
				tnFColor = 0
				lnFIndex = 64
			ENDIF
			IF VARTYPE(tnBColor) = 'N' .AND. BETWEEN(tnBColor, 0, RGB(255, 255, 255))
				tnBColor = FLOOR(tnBColor)
				lnBIndex = 0
			ELSE
				tnBColor = tnFColor
				lnBIndex = lnFIndex
			ENDIF
			IF VARTYPE(tcPatternType) != "C" .OR. EMPTY(tcPatternType)
				tcPatternType = FILL_STYLE_SOLID
			ENDIF
		*-*	Get the fill record id
			IF SEEK(BINTOC(tnWB)+BINTOC(tnFColor)+BINTOC(tnBColor)+tcPatternType, "xl_fills", "fillcolor")
				lnFillId = xl_fills.id
				REPLACE xl_fills.theme     WITH 0, ;
						xl_fills.tint      WITH 0, ;
						xl_fills.fgindexed WITH lnFIndex, ;
						xl_fills.bgindexed WITH lnBIndex IN xl_fills
			ELSE
				lnFillId = this.GetNextId(tnWB, 0, 'xl_fills')
				INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, lnFillId, tnFColor, tnBColor, tcPatternType, 0, 0, lnFIndex, lnBIndex, -1, -1)
			ENDIF
		*-*	Set the fill id to the style definition
			REPLACE xl_cellxfs.fillid    WITH lnFillId, ;
					xl_cellxfs.applyfill WITH 1 IN xl_cellxfs
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleFont		&& Adds to the cell style a font definition
		LPARAMETERS tnWB, tnCellXfsId, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos
		LOCAL lnFontId
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tcFName) != 'C' .OR. EMPTY(tcFName)
				tcFName = 'Calibri'
			ENDIF
			tcFName = PROPER(ALLTRIM(tcFName))
			IF VARTYPE(tnFSize) != 'N' .OR. EMPTY(tnFSize) .OR. tnFSize < 0.5
				tnFSize = 11
			ELSE
				tnFSize = INT(tnFSize)
			ENDIF
			IF VARTYPE(tlBold) != 'L'
				tlBold = False
			ENDIF
			IF VARTYPE(tlItalic) != 'L'
				tlItalic = False
			ENDIF
			IF VARTYPE(tnFColor) = 'N' .AND. BETWEEN(tnFColor, 0, RGB(255, 255, 255))
				tnFColor = FLOOR(tnFColor)
			ELSE
				tnFColor = RGB(0, 0, 0)
			ENDIF
			IF VARTYPE(tlStrikThr) != 'L'
				tlStrikThr = False
			ENDIF
			IF VARTYPE(tcULine) != 'C' .OR. !INLIST(tcULine, UNDERLINE_SINGLE, UNDERLINE_DOUBLE, UNDERLINE_SINGLEACCOUNTING, UNDERLINE_DOUBLEACCOUNTING, UNDERLINE_NONE)
				tcULine = UNDERLINE_NONE
			ENDIF
			IF VARTYPE(tcVPos) != 'C' .OR. !INLIST(tcVPos, FONT_VERTICAL_BASELINE, FONT_VERTICAL_SUBSCRIPT, FONT_VERTICAL_SUPERSCRIPT)
				tcVPos = FONT_VERTICAL_BASELINE
			ENDIF
		*-*	Get the font record id
			IF SEEK(BINTOC(tnWB)+PADR(tcFName, 100) + STR(tnFSize, 5, 1) + TRANSFORM(tlBold) + TRANSFORM(tlItalic) + PADL(tnFColor, 15) + PADR(tcULine, 16) + ;
					TRANSFORM(tlStrikThr) + PADR(tcVPos, 11), "xl_fonts", "cellformat")
		
				lnFontId = xl_fonts.id
				REPLACE xl_fonts.theme   WITH 0, ;
						xl_fonts.tint    WITH 0, ;
						xl_fonts.indexed WITH 0 IN xl_fonts
			ELSE
				lnFontId = this.GetNextId(tnWB, 0, 'xl_fonts')
				INSERT INTO xl_fonts (workbook, id, fname, fsize, fbold, fitalic, fcolor, uline, strkthr, fvpos) ;
					VALUES (tnWB, lnFontId, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos)
			ENDIF
		*-*	Set the font id to the style definition
			REPLACE xl_cellxfs.fontid    WITH lnFontId, ;
					xl_cellxfs.applyfont WITH 1 IN xl_cellxfs
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleHorizAlignment		&& Adds to the cell style horizontal cell alignment
		LPARAMETERS tnWB, tnCellXfsId, tcHorizAlign
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tcHorizAlign) = 'C' .AND. INLIST(tcHorizAlign, CELL_HORIZ_ALIGN_LEFT, CELL_HORIZ_ALIGN_RIGHT, CELL_HORIZ_ALIGN_CENTER)
				REPLACE xl_cellxfs.halign     WITH tcHorizAlign, ;
						xl_cellxfs.applyalign WITH 1 IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleIndent		&& Adds to the style indent definition
		LPARAMETERS tnWB, tnCellXfsId, tnIndent
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tnIndent) = 'N'
				REPLACE xl_cellxfs.indent WITH tnIndent IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleNumericFormat		&& Adds to the style definition numeric format
		LPARAMETERS tnWB, tnCellXfsId, tnNumFmtId
		LOCAL lnNumFmtId
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			DO CASE
				CASE VARTYPE(tnNumFmtId) != 'N'
					RETURN False
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYYHMM
					lcFormatCode = CHRTRAN("[$-409]m|d|yy h:mm", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_DDMMMYYYY_TTAM
					lcFormatCode = CHRTRAN("[$-409]dd|mmm|yyyy h:mm AM/PM;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_DDMMMYYYY_TT24
					lcFormatCode = CHRTRAN("[$-409]dd|mmm|yyyy h:mm;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MMMDDYYYY_TTAM
					lnNumFmtId = this.AddCustomDateTimeFormat(tnWB, '[$-409]mmm d, yyyy h:mm AM/PM;@')
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MMMDDYYYY_TT24
					lnNumFmtId = this.AddCustomDateTimeFormat(tnWB, '[$-409]mmm d, yyyy hh:mm;@')
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYY_TTAM
					lcFormatCode = CHRTRAN("[$-409]m|d|yy h:mm AM/PM;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYY_TT24
					lcFormatCode = CHRTRAN("[$-409]m|d|yy hh:mm;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId < START_NUMERIC_FORMAT_ID
					lnNumFmtId = tnNumFmtId
		
				CASE tnNumFmtId >= START_NUMERIC_FORMAT_ID
					IF SEEK(BINTOC(tnWB)+BINTOC(tnNumFmtId), "xl_numfmts", "id")
						lnNumFmtId = xl_numfmts.id
					ELSE
						IF !this.AddClassDefinedNumericFormats(tnWB, tnNumFmtId)
							RETURN False
						ENDIF
						lnNumFmtId = tnNumFmtId
					ENDIF
		
				OTHERWISE
					RETURN False
			ENDCASE
			REPLACE xl_cellxfs.numfmtid WITH lnNumFmtId IN xl_cellxfs
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleProtection		&& Adds the cell protection setting to the style
		LPARAMETERS tnWB, tnCellXfsId, tnLocked, tnHidden
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tnLocked) = 'N' .AND. BETWEEN(tnLocked, 0, 1) .AND. VARTYPE(tnHidden) = 'N' .AND. BETWEEN(tnHidden, 0, 1)
				REPLACE xl_cellxfs.celllocked WITH tnLocked, ;
						xl_cellxfs.cellhidden WITH tnHidden, ;
						xl_cellxfs.applyprot  WITH 1 IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleTextRotation		&& Adds to the style definition text rotation
		LPARAMETERS tnWB, tnCellXfsId, tnRotation
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tnRotation) = 'N' .AND. BETWEEN(tnRotation, -90, 90)
				REPLACE xl_cellxfs.rotation WITH tnRotation IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleVertAlignment		&& Adds to the cell style vertical cell alignment
		LPARAMETERS tnWB, tnCellXfsId, tcVertAlign
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tcVertAlign) = 'C' .AND. INLIST(tcVertAlign, CELL_VERT_ALIGN_TOP, CELL_VERT_ALIGN_BOTTOM, CELL_VERT_ALIGN_CENTER)
				REPLACE xl_cellxfs.valign     WITH tcVertAlign, ;
						xl_cellxfs.applyalign WITH 1 IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE AddStyleWordWrap		&& Adds to the style definition word wrap
		LPARAMETERS tnWB, tnCellXfsId, tlWordWrap
		IF PCOUNT() < 3
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF VARTYPE(tlWordWrap) = 'L'
				REPLACE xl_cellxfs.wraptext WITH IIF(tlWordWrap, 1, 0) IN xl_cellxfs
				RETURN True
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE AT		&& Fast string replacement for AT() function
		LPARAMETERS tcFindText, toString, tnOccurrence
		LOCAL lnBeg, lcVal, lnLen, lnOccurrence
		IF PCOUNT() = 2
			tnOccurrence = 1
		ENDIF
		lnLen = LEN(tcFindText)
		lnOccurrence = 0
		lnBeg = 1
		DO WHILE lnBeg < toString.Length
			lcVal = SYS(2600, toString.BaseAdr-1 + lnBeg, lnLen)
			IF lcVal == tcFindText
				lnOccurrence = lnOccurrence + 1
				IF lnOccurrence = tnOccurrence
					RETURN lnBeg
				ENDIF
			ENDIF
			lnBeg = lnBeg + 1
		ENDDO
		RETURN 0
	ENDPROC

	PROCEDURE CellFormatPainter		&& Copies the formatting of a source cell to other cells
		LPARAMETERS tnWB, tnSheet, tnSrcRow, tnSrcCol, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		LOCAL lnCellXfs, lnNumDec, llReturn, lnRow, lnCol
		DO CASE
			CASE PCOUNT() < 6
				RETURN False
		
			CASE PCOUNT() = 6
				tnEndRow = tnBegRow
				tnEndCol = tnBegCol
			
			CASE PCOUNT() = 7
				tnEndCol = tnBegCol
		ENDCASE
		IF this.GetCellRecord(tnWB, tnSheet, tnSrcRow, tnSrcCol)
			lnCellXfs = xl_cells.cellxfs
			lnNumDec  = xl_cells.numdec
			FOR lnRow=tnBegRow TO tnEndRow
				FOR lnCol=tnBegCol TO tnEndCol
					IF this.GetCellRecord(tnWB, tnSheet, lnRow, lnCol)
						REPLACE xl_cells.cellxfs WITH lnCellXfs, ;
								xl_cells.numdec  WITH lnNumDec IN xl_cells
					ELSE
						INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
							VALUES (tnWB, tnSheet, lnRow, lnCol, DATA_TYPE_NONE, lnCellXfs, False, lnNumDec, 0)
					ENDIF
				ENDFOR
			ENDFOR
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE CellRefAsciiToIndex		&& Converts a 'AA444' cell reference to the row and column index values
		LPARAMETERS tcCellRef
		LOCAL lnCellCol, lnNdx, lnCellRow, loCellRef
		lnCellCol = this.ColumnAsciiToIndex(tcCellRef)
		lnCellRow = 0
		FOR lnNdx=1 TO LEN(tcCellRef)
			IF ISDIGIT(SUBSTR(tcCellRef, lnNdx))
				lnCellRow = INT(VAL(SUBSTR(tcCellRef, lnNdx)))
				EXIT
			ENDIF
		ENDFOR
		loCellRef = CREATEOBJECT("Empty")
		ADDPROPERTY(loCellRef, "Column", lnCellCol)
		ADDPROPERTY(loCellRef, "Row", lnCellRow)
		RETURN loCellRef
	ENDPROC

	PROCEDURE CheckSheetName		&& Checks the sheet name for valid characters; returns a corrected string (invalid characters converted to underscore _)
		LPARAMETERS tcSheetName
		LOCAL lcSheetName
		*-*	Remove illegal characters from sheet name; replace with underscore character
		lcSheetName = CHRTRAN(tcSheetName, ":\/[]?*", "_______")
		
		*-*	Check for characters converted to xml format that are invalid
		lcSheetName = STRTRAN(lcSheetName, "&#58;", "_")                     && : character
		lcSheetName = STRTRAN(lcSheetName, "&#47;", "_")                     && / character
		lcSheetName = STRTRAN(lcSheetName, "&#92;", "_")                     && \ character
		lcSheetName = STRTRAN(lcSheetName, "&#63;", "_")                     && ? character
		lcSheetName = STRTRAN(lcSheetName, "&#42;", "_")                     && * character
		
		*-*	Check for apostrophe (') at beginning or end (not allowed)
		lcSheetName = ALLTRIM(lcSheetName, 1, "'")
		
		*-*	Verify max length of sheet name not exceeded
		IF LEN(lcSheetName) > LIMITS_MAX_SH_NAME
			IF this.AutoTrimSheetName
				lcSheetName = LEFT(ALLTRIM(lcSheetName), LIMITS_MAX_SH_NAME)
			ELSE
				RETURN .NULL.
			ENDIF
		ENDIF
		IF EMPTY(lcSheetName)
			RETURN .NULL.
		ENDIF
		IF UPPER(lcSheetName) == "HISTORY"
			RETURN .NULL.
		ENDIF
		lcSheetName = this.GetXMLString(lcSheetName)
		IF EMPTY(lcSheetName)
			RETURN .NULL.
		ENDIF
		RETURN lcSheetName
	ENDPROC

	PROCEDURE ClearAutoFilter		&& Clears the column filter for the sheet
		LPARAMETERS tnWB, tnSheet
		IF PCOUNT() != 2
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_sheets", "sheet")
			REPLACE xl_sheets.addfilter   WITH False, ;
					xl_sheets.filtbegcol  WITH 0, ;
					xl_sheets.filtendcol  WITH 0 IN xl_sheets
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE ClearCellValidation		&& Clears/deletes the named range from the workbook
		LPARAMETERS tnWB, tcName
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		IF VARTYPE(tcName) != "C"
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+PADL(tcName, 254, " "), "xl_namerange", "wbrname")
			DELETE IN xl_namerange
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE ClearCellValue		&& Clears the cell value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL llReturn
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			REPLACE xl_cells.celldeleted WITH True IN xl_cells
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE ClearPrintArea		&& Clears the selected sheet's print area
		LPARAMETERS tnWB, tnSheet
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		DELETE FROM xl_namerange ;
			WHERE workbook = tnWB ;
				AND sheet = tnSheet ;
				AND rname = PADL("_xlnm.PRINT_AREA", 50, " ")
		
		IF _TALLY > 0
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE ColumnAsciiToIndex		&& Converts Excel Ascii column value to index value
		LPARAMETERS tcColIndex
		LOCAL lnIndex, lnNdx, lnChr
		lnIndex = 0
		FOR lnNdx=1 TO LEN(tcColIndex)
			lnChr = ASC(UPPER(SUBSTR(tcColIndex, lnNdx, 1)))
			IF BETWEEN(lnChr, 65, 90)
				lnIndex = (lnChr - 64) + (lnIndex * 26)
			ELSE
				EXIT
			ENDIF
		ENDFOR
		RETURN lnIndex
	ENDPROC

	PROCEDURE ColumnIndexToAscii		&& Gets the column ASCII code for the column index
		LPARAMETERS tnCol
		LOCAL lnRemCol, lcColumn, lnPartCol
		IF PCOUNT() = 0 .OR. VARTYPE(tnCol) != "N"
			RETURN ""
		ENDIF
		TRY
			lnRemCol = tnCol
			lcColumn = ""
			DO WHILE lnRemCol > 0
				lnPartCol = MOD(lnRemCol, 26)
				IF lnPartCol = 0
					lnPartCol = 26
				ENDIF
				lnRemCol = (lnRemCol - lnPartCol) / 26
				lcColumn = CHR(lnPartCol + 64)  + lcColumn
			ENDDO
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
		RETURN lcColumn
	ENDPROC

	PROTECTED PROCEDURE ConvertCentimetersToEMU		&& Converts centimeters to English Metric Units for image placement
		LPARAMETERS tnCentimeter
		RETURN tnCentimeter * 360000
	ENDPROC

	PROTECTED PROCEDURE ConvertColorToHex		&& Converts a color value (integer) to Hex representation
		LPARAMETERS tnColor
		LOCAL lnRed, lnGreen, lnBlue, lcHexRed, lcHexGreen, lcHexBlue
		IF VARTYPE(tnColor) = "N"
			lnRed   = this.GetRGBValues(tnColor, 'R')
			lnGreen = this.GetRGBValues(tnColor, 'G')
			lnBlue  = this.GetRGBValues(tnColor, 'B')
			lcHexRed   = RIGHT(TRANSFORM(lnRed, '@0'), 2)
			lcHexGreen = RIGHT(TRANSFORM(lnGreen, '@0'), 2)
			lcHexBlue  = RIGHT(TRANSFORM(lnBlue, '@0'), 2)
			RETURN "FF" + UPPER(lcHexRed + lcHexGreen + lcHexBlue)
		ELSE
			RETURN ""
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE ConvertFileToString		&& Reads a file into a String value
		LPARAMETERS tcFileName, tnConvSetting
		RETURN STRCONV(FILETOSTR(tcFileName), tnConvSetting)
	ENDPROC

	PROTECTED PROCEDURE ConvertHexStringToNumeric		&& ConvertHexStringToNumeric
		LPARAMETERS tcHexNum
		LOCAL lcValue
		IF EMPTY(tcHexNum)
			RETURN 0
		ENDIF
		IF LEN(tcHexNum) > 6
			tcHexNum = SUBSTR(tcHexNum, 3)
		ENDIF
		tcHexNum = UPPER(ALLTRIM(tcHexNum))
		tcHexNum = RIGHT(tcHexNum, 2) + SUBSTR(tcHexNum, 3, 2) + LEFT(tcHexNum, 2)
		lcValue  = "0x" + tcHexNum
		RETURN EVAL(lcValue)
	ENDPROC

	PROCEDURE ConvertPixelsToCentimeters		&& Converts pixels to Centimeters for image placement
		LPARAMETERS tnPixels, tcDirection
		LOCAL lhDeviceHdl, lnPixelsPerInch
		lhDeviceHdl = apiGetDC(0)
		DO CASE
			CASE tcDirection = "W"        && Width
				lnPixelsPerInch = apiGetDeviceCaps(lhDeviceHdl, 88)      && LOGPIXELSX
			CASE tcDirection = "H"        && Height
				lnPixelsPerInch = apiGetDeviceCaps(lhDeviceHdl, 90)      && LOGPIXELSY
		ENDCASE
		lhDeviceHdl = apiReleaseDC(0,lhDeviceHdl)
		RETURN tnPixels / lnPixelsPerInch * 2.54
	ENDPROC

	PROCEDURE ConvertPixelsToExcelUnits		&& Converts Pixel measurement to Excel measurement
		LPARAMETERS tnPixels
		RETURN tnPixels * 0.152542
	ENDPROC

	PROCEDURE ConvertRangeToColumnRowValues		&& Converts a given range format to the column and row values
		LPARAMETERS tcCellRange
		LOCAL loRange, lcBegRange, lcEndRange, lnNdx
		loRange = createobject("Empty")
		ADDPROPERTY(loRange, "BegCol", 0)
		ADDPROPERTY(loRange, "BegRow", 0)
		ADDPROPERTY(loRange, "EndCol", 0)
		ADDPROPERTY(loRange, "EndRow", 0)
		
		tcCellRange = CHRTRAN(tcCellRange, "$", "")
		lcBegRange  = GETWORDNUM(tcCellRange, 1, ":")
		lcEndRange  = GETWORDNUM(tcCellRange, 2, ":")
		
		lnNdx = this.GetAlphaNumericSplit(lcBegRange)
		DO CASE
			CASE lnNdx = 0                  && Column only range
				loRange.BegCol = this.ColumnAsciiToIndex(lcBegRange)
				loRange.BegRow = 0
		
			CASE lnNdx = 1                  && Row only range
				loRange.BegCol = 0
				loRange.BegRow = CAST(lcBegRange AS I)
		
			OTHERWISE
				loRange.BegCol = this.ColumnAsciiToIndex(LEFT(lcBegRange, lnNdx-1))
				loRange.BegRow = CAST(SUBSTR(lcBegRange, lnNdx) AS I)
		ENDCASE
		
		lnNdx = this.GetAlphaNumericSplit(lcEndRange)
		IF EMPTY(lcEndRange)
			loRange.EndCol = 0
			loRange.EndRow = 0
		ELSE
			DO CASE
				CASE lnNdx = 0                  && Column only range
					loRange.EndCol = this.ColumnAsciiToIndex(lcEndRange)
					loRange.EndRow = 0
			
				CASE lnNdx = 1                  && Row only range
					loRange.EndCol = 0
					loRange.EndRow = CAST(lcEndRange AS I)
		
				OTHERWISE
					loRange.EndCol = this.ColumnAsciiToIndex(LEFT(lcEndRange, lnNdx-1))
					loRange.EndRow = CAST(SUBSTR(lcEndRange, lnNdx) AS I)
			ENDCASE
		ENDIF
		RETURN loRange
	ENDPROC

	PROTECTED PROCEDURE ConvertStringToFile		&& Converts a encoded string to file
		LPARAMETERS tcFileContent, tcFileName, tnConvSetting
		IF PCOUNT() = 2
			STRTOFILE(tcFileContent, tcFileName)
		ELSE
			STRTOFILE(STRCONV(tcFileContent, tnConvSetting), tcFileName)
		ENDIF
	ENDPROC

	PROCEDURE CopyStyle		&& Copies the selected style to a new style Id
		LPARAMETERS tnWB, tnCellXfsId
		LOCAL lnId
		IF PCOUNT() != 2
			RETURN -1
		ENDIF
		SELECT * FROM xl_cellxfs WHERE BINTOC(workbook)+BINTOC(id) = BINTOC(tnWB)+BINTOC(tnCellXfsId) INTO CURSOR t_cellxfs
		IF _TALLY > 0
			lnId = this.GetNextId(tnWB, 0, "xl_cellxfs")
			INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, rotation, celllocked, cellhidden) ;
				VALUES (tnWB, lnId, t_cellxfs.numFmtId, t_cellxfs.fontId, t_cellxfs.fillId, t_cellxfs.borderId, t_cellxfs.halign, t_cellxfs.valign, t_cellxfs.indent, ;
						t_cellxfs.wraptext, t_cellxfs.rotation, t_cellxfs.celllocked, t_cellxfs.cellhidden)
		ELSE
			lnId = -1
		ENDIF
		USE IN SELECT('t_cellxfs')
		RETURN lnId
	ENDPROC

	PROTECTED PROCEDURE CreateExcelFile		&& Creates the Excel file from the components
		LPARAMETERS tcTempPath, tcFilePath, tcWBName, tlMacroEnabled
		LOCAL lcZipFile, loException, lcZipPath, lhFile, llCreated, llError, lcTempZipFile
		IF PCOUNT() = 3
			tlMacroEnabled = False
		ENDIF
		lcZipPath = ALLTRIM(tcFilePath)
		IF EMPTY(lcZipPath)
			lcZipPath = SYS(5) + SYS(2003)
		ENDIF
		lcZipFile = ADDBS(lcZipPath) + FORCEEXT(ALLTRIM(tcWBName), "zip")
		
		*-*	Delete any prior file with same Excel name
		IF FILE(FORCEEXT(lcZipFile, "xlsx"))
			TRY
				ERASE (FORCEEXT(lcZipFile, "xlsx"))
				llError = False
		
			CATCH TO loException
				this.ErrorLevelId = 10
				RAISEEVENT(this, "OnShowErrorMessage", 10, "Error - unable to delete existing file" + CR + loException.Message)
				llError = True
			ENDTRY
			IF llError
				RETURN False
			ENDIF
		ENDIF
		
		*-*	Validate that the file name and path are valid
		TRY
			lhFile = FCREATE(lcZipFile, 0)
			FCLOSE(lhFile)
			ERASE (lcZipFile)
		
		CATCH TO loException
			this.ErrorLevelId = 11
			RAISEEVENT(this, "OnShowErrorMessage", 11, "CreateExcelFile - Failed to create Zip file"+ CR + loException.Message)
			RETURN False
		ENDTRY
		
		*-*	Add the files to the zip and rename to xlsx
		lcTempZipFile = FORCEPATH(lcZipFile, SYS(2023))              && Use of temp file/directory provided by Doug Hennig to support share drives during zip creation process
		llCreated = this.AddFilesToZip(tcTempPath, lcTempZipFile)
		IF llCreated
			TRY
				IF tlMacroEnabled
					apiMoveFile(lcTempZipFile, FORCEEXT(lcZipFile, "xlsm"))
				ELSE
					apiMoveFile(lcTempZipFile, FORCEEXT(lcZipFile, "xlsx"))
				ENDIF
			
			CATCH TO loException
				llCreated = False
				this.ErrorLevelId = 13
				RAISEEVENT(this, "OnShowErrorMessage", 13, "CreateExcelFile - Rename failed"+ CR + loException.Message)
				SET STEP ON
			ENDTRY
		ENDIF
		RETURN llCreated
	ENDPROC

	PROCEDURE CreateFormatStyle		&& Creates a new format style definition
		LPARAMETERS tnWB
		LOCAL lnRelId, lnId
		IF PCOUNT() > 0 .AND. SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			lnId = this.GetNextId(tnWB, 0, "xl_cellxfs")
			INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, rotation, celllocked, cellhidden) ;
				VALUES (tnWB, lnId, 0, 0, 0, 0, "", "", 0, 0, 0, 1, 0)
		
		*-*	Check if the styles.xml has been defined for the workbook; if not, create the base style.xml definition
			IF !SEEK(BINTOC(tnWB)+BINTOC(0)+RELS_TYPE_STYLES, "xl_relationships", "reltype")
				lnRelId = this.GetNextId(tnWB, 0, "xl_workbook_rels")
				INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) VALUES (tnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_STYLES, "styles.xml")
		
				this.SetDefaultFont(tnWB, this.DefaultFont, this.DefaultFontSize)
				this.SetDefaultBorder(tnWB, BORDER_STYLE_NONE)
		
				INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, celllocked, cellhidden) ;
					VALUES (tnWB, 0, 0, 0, 0, 0, "", "", 0, 0, 1, 0)
		
				INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, 0, 0, 0, FILL_STYLE_NONE, 0, 0, 0, 0, -1, -1)
		
				INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, 1, 0, 0, FILL_STYLE_GRAY125, 0, 0, 0, 0, -1, -1)
			ENDIF
			RETURN lnId
		ELSE
			RETURN -1
		ENDIF
	ENDPROC

	PROCEDURE CreateInLineFormatText		&& Creates an in-line text formatted definition
		LPARAMETERS tnWB, tcCellText
		LOCAL loInline
		loInline = CREATEOBJECT("Empty")
		ADDPROPERTY(loInline, "Workbook", tnWB)
		ADDPROPERTY(loInline, "StringId", .NULL.)
		ADDPROPERTY(loInline, "StringValue", tcCellText)
		ADDPROPERTY(loInline, "Count", 0)
		ADDPROPERTY(loInline, "Characters[1]")
		loInline.Characters[1] = .NULL.
		RETURN loInline
	ENDPROC

	PROCEDURE CreateWorkbook		&& Creates a new workbook object
		LPARAMETERS tcName
		LOCAL lcWBName, lcPath, lnWB
		IF PCOUNT() = 1
			tlLoadDefaults = True
		ENDIF
		lcWBName = JUSTSTEM(tcName)
		lcPath   = JUSTPATH(tcName)
		IF EMPTY(lcPath)
			lcPath = SYS(5) + SYS(2003)
			tcName = ADDBS(lcPath) + lcWBName + "." + JUSTEXT(tcName)
		ENDIF
		IF SEEK(PADR(UPPER(tcName), 240, "_"), "xl_workbooks", "fullname")
			lnWB = 0
		ELSE
			INSERT INTO xl_workbooks (wbname, filepath, fullname, macroenabled) VALUES (lcWBName, lcPath, PADR(UPPER(tcName), 240, "_"), False)
		
			lnWB = xl_workbooks.workbook
			this.SetDefaultFont(lnWB, this.DefaultFont, this.DefaultFontSize)        && Change recommended by Doug Hennig; 2017-06-12
			this.SetDefaultBorder(lnWB, BORDER_STYLE_NONE)
		
			INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) VALUES (lnWB, 0, RELS_SRC_WORKBOOK, 1, RELS_TYPE_STYLES, "styles.xml")
			INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) VALUES (lnWB, 0, RELS_SRC_WORKBOOK, 2, RELS_TYPE_SHAREDSTRINGS, "sharedStrings.xml")
			this.SetLastId(lnWB, 0, 2, "xl_workbook_rels")
		
			INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, celllocked, cellhidden) ;
				VALUES (lnWB, 0, 0, 0, 0, 0, "", "", 0, 0, 1, 0)
		
			INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
				VALUES (lnWB, 0, 0, 0, FILL_STYLE_NONE, 0, 0, 0, 0, -1, -1)
		
			INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
				VALUES (lnWB, 1, 0, 0, FILL_STYLE_GRAY125, 0, 0, 0, 0, -1, -1)
				
			INSERT INTO xl_contenttypes (workbook, extension, contenttype) ;
				VALUES (lnWB, "rels", "application/vnd.openxmlformats-package.relationships+xml")
		
			INSERT INTO xl_contenttypes (workbook, extension, contenttype) ;
				VALUES (lnWB, "xml", "application/xml")
		
		ENDIF
		RETURN lnWB
	ENDPROC

	PROTECTED PROCEDURE CreateWorkbookEx		&& Internal method for creating a workbook
		LPARAMETERS tcName
		LOCAL lcWBName, lcPath, lnWB
		lcWBName = JUSTSTEM(tcName)
		lcPath   = JUSTPATH(tcName)
		IF EMPTY(lcPath)
			lcPath = SYS(5) + SYS(2003)
			tcName = ADDBS(lcPath) + lcWBName + "." + JUSTEXT(tcName)
		ENDIF
		IF SEEK(PADR(UPPER(tcName), 240, "_"), "xl_workbooks", "fullname")
			lnWB = 0
		ELSE
			INSERT INTO xl_workbooks (wbname, filepath, fullname, macroenabled) VALUES (lcWBName, lcPath, PADR(UPPER(tcName), 240, "_"), False)
			lnWB = xl_workbooks.workbook
		ENDIF
		RETURN lnWB
	ENDPROC

	PROTECTED PROCEDURE CreateWorkingCursors		&& Creates the working cursors for worksheet definitions
		LOCAL lcCollate, lcAlias
		IF USED('xl_strings')
			RETURN
		ENDIF
		lcAlias   = ALIAS()
		lcCollate = SET('COLLATE')
		SET COLLATE TO 'MACHINE'
		DO CASE
			CASE this.CodePage = 620                                         && Mazovia (Polish) MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 620 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 620 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 737                                         && Greek MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 737 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 737 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 852                                         && Eastern European MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 852 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 852 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 857                                         && Turkish MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 857 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 857 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 861                                         && Icelandic MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 861 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 861 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 865                                         && Nordic MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 865 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 865 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 866                                         && Russian MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 866 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 866 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 874                                         && Thai Windows
				CREATE CURSOR xl_strings CODEPAGE = 874 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 874 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 895                                         && Kamenicky (Czech) MS-DOS
				CREATE CURSOR xl_strings CODEPAGE = 895 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 895 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 932                                         && Japanese Windows
				CREATE CURSOR xl_strings CODEPAGE = 932 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 932 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 936                                         && Chinese Simplified (PRC, Singapore) Windows
				CREATE CURSOR xl_strings CODEPAGE = 936 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 939 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 949                                         &&Korean Windows
				CREATE CURSOR xl_strings CODEPAGE = 949 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 949 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 950                                         && Traditional Chinese (Hong Kong SAR, Taiwan) Windows
				CREATE CURSOR xl_strings CODEPAGE = 950 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 950 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1250                                        && Eastern European Windows
				CREATE CURSOR xl_strings CODEPAGE = 1250 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1250 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1251                                        && Russian Windows
				CREATE CURSOR xl_strings CODEPAGE = 1251 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1251 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1252                                        && Windows ANSI
				CREATE CURSOR xl_strings CODEPAGE = 1252 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1252 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1253                                        && Greek Windows
				CREATE CURSOR xl_strings CODEPAGE = 1253 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1253 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1254                                        && Turkish Windows
				CREATE CURSOR xl_strings CODEPAGE = 1254 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1255 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1255                                        && Hebrew Windows
				CREATE CURSOR xl_strings CODEPAGE = 1255 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1255 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			CASE this.CodePage = 1256                                        && Arabic Windows
				CREATE CURSOR xl_strings CODEPAGE = 1256 (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells CODEPAGE = 1256 (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		
			OTHERWISE
				CREATE CURSOR xl_strings (id I, workbook I, checksum C(230), stringxml M, stringval M, presvspace L, formatted L)
				CREATE CURSOR xl_cells (workbook I, sheet I, cellrow I, cellcol I, cellvalue M, datatype C(1), formulatype C(10), formulasi I, ;
					stringid I, cellxfs I, numdec I, celldeleted L, validndx I)
		ENDCASE
		
		SELECT xl_strings
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+checksum TAG stringndx
		INDEX ON workbook TAG workbook
		SET INDEX TO
		
		SELECT xl_cells
		INDEX ON sheet TAG sheet
		INDEX ON cellrow TAG cellrow
		INDEX ON cellcol TAG cellcol
		INDEX ON workbook TAG workbook
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(cellrow)+BINTOC(cellcol) TAG reverse DESCENDING
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(cellrow)+BINTOC(cellcol) TAG cellindex
		SET INDEX TO
		
		CREATE CURSOR xl_strformat (workbook I, id I, index I, stringxml M, stringval M, fbold L, fitalic L, fcolor I, fname C(100), fsize N(5,1), uline C(16), strkthr L, ;
			fvpos C(11), theme I, tint F(20,15), indexed I, presvspace L)
		INDEX ON BINTOC(workbook)+BINTOC(id)+PADL(index, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(id)+BINTOC(index) TAG id FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_workbooks (workbook I AUTOINC NEXTVALUE 1, wbname C(200), filepath C(200), fullname C(240), protection L, password C(50), algorithm C(10), macroenabled L)
		INDEX ON BINTOC(workbook) TAG workbook
		INDEX ON UPPER(fullname) TAG fullname
		INDEX ON PADR(UPPER(ALLTRIM(wbname)), 200, "_") TAG wbname
		SET INDEX TO
		
		CREATE CURSOR xl_sheets (workbook I, sheet I, shname C(30), state I, gridlines I, mleft N(6,3), mright N(6,3), mtop N(6,3), mbot N(6,3), mheader N(6,3), ;
			mfooter N(6,3), shdeleted L, xsplit I, ysplit I, prnorient I, papersize I, paperwidth I, paperheight I, paperdimen C(2), scale I, fittowidth I, fittoheight I, ;
			tabcolorndx I, tabcolorrgb C(8), outline L, grpsumbelow I, grpsumright I, protection L, password C(50), algorithm C(10), autofilter L, deletecol L, ;
			deleterow L, formatcell L, formatcol L, formatrow L, insertcol L, insertrow L, inserthyper L, objects L, pivottbl L, scenarios L, sellocked L, selunlocked L, ;
			sort L, addfilter L, filtbegcol I, filtendcol I, zoomscale I, prnopthead I, prnoptgrid I, draft I, blackwhite I, pagescale I, fittopage I, toplfcell C(20), ;
			tabselctd I, activcell C(20), prnoptctrv I, prnoptctrh I)
		INDEX ON workbook TAG workbook
		INDEX ON BINTOC(workbook)+UPPER(ALLTRIM(shname)) TAG shname
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_cellxfs (workbook I, id I, numFmtId I, fontId I, fillId I, borderId I, halign C(8), valign C(8), indent I, wraptext I, rotation I, ;
								  celllocked I, cellhidden I, applyfont I, applyborder I, applyalign I, applyprot I, applyfill I)
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(numFmtId)+BINTOC(fontId)+BINTOC(fillId) TAG gridstyle FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(numFmtId)+BINTOC(fontId)+BINTOC(fillId)+BINTOC(borderId)+BINTOC(indent)+BINTOC(wraptext)+BINTOC(rotation)+ALLTRIM(halign)+ALLTRIM(valign) TAG cellxfs FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(fontId)+BINTOC(fillId)+BINTOC(borderId)+BINTOC(indent)+BINTOC(wraptext)+BINTOC(rotation)+ALLTRIM(halign)+ALLTRIM(valign) TAG cellformat FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(fontId)+BINTOC(fillId)+BINTOC(borderId)+BINTOC(indent)+BINTOC(wraptext)+BINTOC(rotation)+BINTOC(celllocked)+BINTOC(cellhidden)+ALLTRIM(halign)+ALLTRIM(valign) TAG cellprot FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_fonts (workbook I, id I, fname C(100), fsize N(5,1), fbold L, fitalic L, fcolor I, uline C(16), strkthr L, ;
							   fvpos C(11), theme I, tint F(20,15), indexed I)
		INDEX ON BINTOC(workbook)+fname + STR(fsize, 5, 1) + TRANSFORM(fbold) + TRANSFORM(fitalic) + PADL(fcolor, 15) + uline + TRANSFORM(strkthr) + fvpos TAG cellformat
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_fills (workbook I, id I, patttype C(25), theme I, tint F(20,15), fgcolor I, bgcolor I, fgindexed I, bgindexed I, fgtheme I, bgtheme I)
		INDEX ON BINTOC(workbook)+BINTOC(fgcolor)+BINTOC(bgcolor)+patttype TAG fillcolor
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_borders (workbook I, id I, lstyle C(20), lcolor I, ltheme I, ltint I, lindexed I, rstyle C(20), rcolor I, rtheme I, rtint I, rindexed I, ;
								 tstyle C(20), tcolor I, ttheme I, ttint I, tindexed I, bstyle C(20), bcolor I, btheme I, btint I, bindexed I, dstyle C(20), ;
								 dcolor I, dtheme I, dtint I, dindexed I, diagdn I, diagup I, theme I, tint F(20,15))
		INDEX ON BINTOC(workbook) + ALLTRIM(lstyle) + BINTOC(lcolor) + ALLTRIM(rstyle) + BINTOC(rcolor) + ALLTRIM(tstyle) + BINTOC(tcolor) + ALLTRIM(bstyle) + BINTOC(bcolor) + ;
				 ALLTRIM(dstyle) + BINTOC(dcolor) + BINTOC(diagdn) + BINTOC(diagup) TAG cellborder
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_mergecells (workbook I, sheet I, begrow I, begcol I, endrow I, endcol I)
		INDEX ON workbook TAG workbook
		INDEX ON sheet TAG sheet
		INDEX ON begrow TAG begrow
		INDEX ON begcol TAG begcol
		INDEX ON endrow TAG endrow
		INDEX ON endcol TAG endcol
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(begrow)+BINTOC(begcol) TAG mergecell FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_columns (workbook I, sheet I, col I, width N(16,9), bestfit L, collevel I, collapsed L, hidden I)
		INDEX ON sheet TAG sheet
		INDEX ON workbook TAG workbook
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(col) TAG col
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(col) TAG reverse DESCENDING
		SET INDEX TO
		
		CREATE CURSOR xl_filters (workbook I, sheet I, col I, andoperator L, filtoper C(20), filtvalue C(100))
		INDEX ON sheet TAG sheet
		INDEX ON workbook TAG workbook
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG colfilter
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(col) TAG col
		SET INDEX TO
		
		CREATE CURSOR xl_hdrfooterdefn (workbook I, sheet I, alignmargin L, difffirstpg L, diffoddeven L, scalewdoc L)
		INDEX ON workbook TAG workbook
		INDEX ON sheet TAG sheet
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG wbsheet FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_hdrfootertext (workbook I, sheet I, page I, section I, text C(200), fontname C(50), fontsize I, fontstyle I, fontcolor I)
		INDEX ON workbook TAG workbook
		INDEX ON sheet TAG sheet
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(page)+BINTOC(section) TAG hdrfootr FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_validation (workbook I, sheet I, validndx I AUTOINC NEXTVALUE 1, vtype I, vstyle I, voperator I, errmsg C(100), errtitle C(100), ;
									 allowblank L, showinpmsg L, showerrmsg L, vprompt C(100), formula L, formula1 C(254), formula2 C(254))
		INDEX ON BINTOC(validndx) TAG validndx FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG wbshindex FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(validndx) TAG validindex FOR !DELETED()
		INDEX ON LEFT(formula1, 240) TAG formula1 FOR !DELETED()
		INDEX ON LEFT(formula2, 240) TAG formula2 FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_namerange (workbook I, rname C(50), scope I, comment C(254), sheet I, begrow I, begcol I, endrow I, endcol I)
		INDEX ON BINTOC(workbook)+rname TAG wbrname FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_numfmts (workbook I, id I, tempid I, formatxml M, formatcode M, applydec L)
		INDEX ON BINTOC(workbook)+PADL(id, 10, '0') TAG sortorder FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(id) TAG id FOR !DELETED()
		INDEX ON BINTOC(workbook)+PADR(formatcode, 25, "X") TAG formatcode FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_rows (workbook I, sheet I, row I, maxcol I, height N(14,7), rowlevel I, collapsed L, hidden I)
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(row) TAG row FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(row) TAG reverse DESCENDING
		SET INDEX TO
		
		CREATE CURSOR xl_contenttypes (workbook I, extension C(5), contenttype C(60))
		INDEX ON BINTOC(workbook) TAG workbook FOR !DELETED()
		INDEX ON BINTOC(workbook)+extension TAG extension FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_ndxcolors (workbook I, indexid I, rgbcolor C(8))
		INDEX ON BINTOC(workbook)+rgbcolor TAG rgbcolor FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(indexid) TAG indexid FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_mrucolors (workbook I, indexid I, rgbcolor C(8))
		INDEX ON BINTOC(workbook)+rgbcolor TAG rgbcolor FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(indexid) TAG indexid FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_relationships (workbook I, sheet I, relsrc C(1), relid I, reltype C(1), reltypeunk C(200), target C(200), filecontent M, relscontent M, external L, globalid I)
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+relsrc TAG relsrc FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+relsrc+reltype TAG relsrctype FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+reltype TAG reltype FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(relid) TAG relid FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(relid)+relsrc TAG relidsrc FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(relid)+reltype TAG relidtype FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_images (workbook I, sheet I, relid I, anchor C(1), imgmove C(8), begcol I, begcoloff I, begrow I, begrowoff I, ;
								 endcol I, endcoloff I, endrow I, endrowoff I, cx I, cy I)
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(relid) TAG relid FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(begrow)+BINTOC(begcol)+BINTOC(endrow)+BINTOC(endcol) TAG position FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_shapes (workbook I, sheet I, id I, imgmove C(8), begcol I, begcoloff I, begrow I, begrowoff I, endcol I, endcoloff I, endrow I, endrowoff I, ;
								 x I, cx I, y I, cy I, shpname C(25), descrptn C(25), prstgeom C(30), shpfill L)
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(id) TAG id FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(begrow)+BINTOC(begcol)+BINTOC(endrow)+BINTOC(endcol) TAG position FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_shptext (workbook I, sheet I, id I, shptext M, anchor C(8), insetb I, insett I, insetl I, insetr I, wraptext L, vertovrflo C(8), horzovrflo C(8), ;
								  rotation I, fontname C(25), fontsize I, fontbold L, fontitalic L, fontuline C(1))
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(id) TAG id FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_extsheets (workbook I, extid I, relid I, sheetname C(30))
		INDEX ON BINTOC(workbook)+BINTOC(extid) TAG extid FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(extid)+BINTOC(relid) TAG relid FOR !DELETED()
		
		CREATE CURSOR xl_extcells (workbook I, extid I, cellrow I, cellcol I, cellvalue M)
		INDEX ON BINTOC(workbook)+BINTOC(extid) TAG extid FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_hyperlinks (workbook I, sheet I, begrow I, begcol I, endrow I, endcol I, targettype C(1), tgtsheet I, tgtbegrow I, tgtbegcol I, tgtendrow I, tgtendcol I, ;
									 location M, display C(200), relid I, tgtsheetname C(30))
		INDEX ON BINTOC(workbook)+BINTOC(sheet) TAG sheet FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(relid) TAG relid FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(begrow)+BINTOC(begcol)+BINTOC(endrow)+BINTOC(endcol) TAG hyperlink FOR !DELETED()
		SET INDEX TO
		
		CREATE CURSOR xl_formulas (workbook I, sheet I, cellrow I, cellcol I, formulatype C(10), si I, ref M, formrel C(1), cellformula M, parsed M)
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(cellrow)+BINTOC(cellcol) TAG cell FOR !DELETED()
		INDEX ON BINTOC(workbook)+BINTOC(sheet)+BINTOC(si) TAG si FOR !EMPTY(cellformula) .AND. !DELETED()
		SET INDEX TO
		
		SET COLLATE TO lcCollate
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE CreateWorkingDirectories		&& Creates the working directories for the XML files
		LOCAL lcDir, loException, lnCnt
		lnCnt = 0
		TRY
		*-*	Get a unique directory name that is not used  [suggestion by Doug Hennig]
			lcDir = ADDBS(ADDBS(SYS(2023)) + SYS(2015))
			DO WHILE DIRECTORY(lcDir)
				INKEY(0.1, 'H')
				lcDir = ADDBS(ADDBS(SYS(2023)) + SYS(2015))
				lnCnt = lnCnt + 1
				IF lnCnt > 20
					THROW "Not able to create a unique directory name in CreateWorkingDirectories() method"
				ENDIF
			ENDDO
		
		*-*	Build the directory structure for the workbook files
			MKDIR (lcDir)
			MKDIR (lcDir + "_rels")
			MKDIR (lcDir + "docProps")
			MKDIR (lcDir + "xl")
			MKDIR (lcDir + "xl\_rels")
			MKDIR (lcDir + "xl\drawings")
			MKDIR (lcDir + "xl\drawings\_rels")
			MKDIR (lcDir + "xl\externalLinks")
			MKDIR (lcDir + "xl\externalLinks\_rels")
			MKDIR (lcDir + "xl\media")
			MKDIR (lcDir + "xl\printerSettings")
			MKDIR (lcDir + "xl\theme")
			MKDIR (lcDir + "xl\worksheets")
			MKDIR (lcDir + "xl\worksheets\_rels")
		
		CATCH TO loException
			lcDir = .NULL.
			RAISEEVENT(this, "OnShowErrorMessage", 2, loException.Message)
		ENDTRY
		IF ISNULL(lcDir)                   && Correction identified by Doug Hennig
			RETURN lcDir
		ELSE
			RETURN ADDBS(lcDir)
		ENDIF
	ENDPROC

	PROCEDURE datetimeseparator_access
		RETURN ALLTRIM(this.DateTimeSeparator, 1, '"', "'", '[', ']')
	ENDPROC

	PROCEDURE DebugMethod		&& Used for debugging only
		LOCAL lcShFile, lcSheet, loSheet
		lcShFile = GETFILE()
		IF !EMPTY(lcShFile)
			lcSheet = FILETOSTR(lcShFile)
			loSheet = this.LoadSheetXml(lcSheet)
			SET STEP ON
		ENDIF
	ENDPROC

	PROCEDURE Debug_Assign
		LPARAMETERS tlDebug
		*IF tlDebug
		*	SET DEBUGOUT TO "VFPXWorkbookXLSXDebug.txt"
		*ENDIF
		this.Debug = tlDebug
	ENDPROC

	PROTECTED PROCEDURE DeclareDLL		&& Declares the required support Win32 API
		DECLARE Sleep                   IN kernel32 AS apiSleep INTEGER tnTime
		DECLARE INTEGER DeleteFile      IN kernel32 AS apiDeleteFile STRING lpFileName
		DECLARE INTEGER RemoveDirectory IN kernel32 AS apiRemoveDirectory STRING lpPathName
		DECLARE INTEGER ShellExecute    IN Shell32  AS apiShellExecute LONG nhWnd, STRING lpAction, STRING lpFile, STRING lpParams, STRING lpDirectory, INTEGER nShowCmd
		DECLARE INTEGER GetDeviceCaps   IN GDI32    AS apiGetDeviceCaps LONG hDC, INTEGER nIndex
		DECLARE INTEGER ReleaseDC       IN User32   AS apiReleaseDC LONG nhWnd, LONG hDC
		DECLARE INTEGER HeapAlloc       IN Win32Api AS apiHeapAlloc INTEGER, INTEGER, INTEGER
		DECLARE INTEGER HeapFree        IN Win32APi AS apiHeapFree INTEGER, INTEGER, INTEGER
		DECLARE LONG GetProcessHeap     IN Win32API AS apiGetProcessHeap
		DECLARE LONG MoveFile           IN Win32API AS apiMoveFile STRING SourceFileName, STRING DestFileName
		DECLARE LONG GetDC              IN User32   AS apiGetDC LONG nhWnd
	ENDPROC

	PROCEDURE DeleteAllWorkbooks		&& Deletes all workbooks
		LOCAL loException
		TRY
			this.DestroyWorkingCursors()
			this.CreateWorkingCursors()
			this.InitalizeIdValues()
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
	ENDPROC

	PROCEDURE DeleteCell		&& Deletes the selected cell
		LPARAMETERS tnWB, tnSh, tnRow, tnCol, tnShift
		DO CASE
			CASE PCOUNT() < 4
				RETURN False
		
			CASE PCOUNT() = 4
				tnShift = SHIFT_CELLS_LEFT
		
			CASE VARTYPE(tnShift) != "N" .OR. !INLIST(tnShift, SHIFT_CELLS_LEFT, SHIFT_CELLS_UP)
				tnShift = SHIFT_CELLS_LEFT
		ENDCASE
		DELETE FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellrow = tnRow .AND. xl_cells.cellcol = tnCol IN xl_cells
		DO CASE
			CASE tnShift = SHIFT_CELLS_LEFT
				REPLACE xl_cells.cellcol WITH xl_cells.cellcol - 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellrow = tnRow .AND. xl_cells.cellcol > tnCol IN xl_cells
		
			CASE tnShift = SHIFT_CELLS_UP
				REPLACE xl_cells.cellrow WITH xl_cells.cellrow - 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellcol = tnCol .AND. xl_cells.cellrow > tnRow IN xl_cells
		
			OTHERWISE
				RETURN False
		ENDCASE
		RETURN True
	ENDPROC

	PROCEDURE DeleteColumn		&& Deletes the selected Column
		LPARAMETERS tnWB, tnSheet, tnCol
		DELETE FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellcol = tnCol IN xl_cells
		REPLACE xl_cells.cellcol WITH xl_cells.cellcol - 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellcol > tnCol IN xl_cells
		
		DELETE FOR xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSh .AND. xl_columns.col = tnCol IN xl_columns
		REPLACE xl_columns.col WITH xl_columns.col - 1 FOR xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSh .AND. xl_columns.col > tnCol IN xl_columns
		RETURN True
	ENDPROC

	PROCEDURE DeleteHyperLink		&& Deletes the selected hyperlink
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		IF PCOUNT() = 6
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnBegRow)+BINTOC(tnBegCol)+BINTOC(tnEndRow)+BINTOC(tnEndCol), "xl_hyperlinks", "hyperlink")
				IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_hyperlinks.relid)+RELS_TYPE_HYPERLINK, "xl_relationships", "relidtype")
					DELETE IN xl_relationships
				ENDIF
				DELETE IN xl_hyperlinks
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE DeleteImage		&& Deletes the image from the sheet
		LPARAMETERS tnWB, tnSheet, tnRelId
		IF PCOUNT() = 3
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRelId), "xl_images", "relid") ;
			.AND. SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRelId)+RELS_TYPE_IMAGE, "xl_relationships", "relidtype")
				DELETE IN xl_relationships
				DELETE IN xl_images
			ELSE
				RETURN False
			ENDIF
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE DeleteRow		&& Deletes the selected row
		LPARAMETERS tnWB, tnSh, tnRow
		DELETE FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellrow = tnRow IN xl_cells
		REPLACE xl_cells.cellrow WITH xl_cells.cellrow - 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSh .AND. xl_cells.cellrow > tnRow IN xl_cells
		
		DELETE FOR xl_rows.workbook = tnWB .AND. xl_rows.sheet = tnSh .AND. xl_rows.row = tnRow IN xl_rows
		REPLACE xl_rows.row WITH xl_rows.row - 1 FOR xl_rows.workbook = tnWB .AND. xl_rows.sheet = tnSh .AND. xl_rows.row > tnRow IN xl_rows
		RETURN True
	ENDPROC

	PROCEDURE DeleteSheet		&& Deletes the sheet from the Workbook
		LPARAMETERS tnWB, txSheet
		LOCAL lnSheet, lxReturn
		lxReturn = False
		DO CASE
			CASE VARTYPE(txSheet) = "C"
				IF SEEK(BINTOC(tnWB)+UPPER(txSheet), "xl_sheets", "shname") .AND. SEEK(BINTOC(tnWB)+BINTOC(xl_sheets.sheet), "xl_relationships", "sheet")
					lnSheet = xl_sheets.sheet
					REPLACE xl_sheets.shdeleted WITH True IN xl_sheets
					DELETE IN xl_sheets
					DELETE IN xl_relationships
					lxReturn = True
				ENDIF
		
			CASE VARTYPE(txSheet) = "N"
				IF SEEK(BINTOC(tnWB)+BINTOC(txSheet), "xl_sheets", "sheet") .AND. SEEK(BINTOC(tnWB)+BINTOC(xl_sheets.sheet), "xl_relationships", "sheet")
					lnSheet = txSheet
					REPLACE xl_sheets.shdeleted WITH True IN xl_sheets
					DELETE IN xl_sheets
					DELETE IN xl_relationships
					lxReturn = True
				ENDIF
		ENDCASE
		IF lxReturn
			REPLACE xl_cells.celldeleted WITH True FOR workbook = tnWB .AND. sheet = lnSheet IN xl_cells
			DELETE FOR workbook = tnWB .AND. sheet = lnSheet IN xl_cells
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROCEDURE DeleteWorkbook		&& Deletes the selected workbook
		LPARAMETERS tnWB
		LOCAL llSuccess, lnNdx
		TRY
			REPLACE xl_sheets.shdeleted  WITH True FOR workbook = tnWB IN xl_sheets
			REPLACE xl_cells.celldeleted WITH True FOR workbook = tnWB IN xl_cells
			DELETE FOR workbook = tnWB IN xl_workbooks
			DELETE FOR workbook = tnWB IN xl_sheets
			DELETE FOR workbook = tnWB IN xl_cells
			DELETE FOR workbook = tnWB IN xl_strformat
			DELETE FOR workbook = tnWB IN xl_strings
			DELETE FOR workbook = tnWB IN xl_cellxfs
			DELETE FOR workbook = tnWB IN xl_fonts
			DELETE FOR workbook = tnWB IN xl_fills
			DELETE FOR workbook = tnWB IN xl_borders
			DELETE FOR workbook = tnWB IN xl_mergecells
			DELETE FOR workbook = tnWB IN xl_columns
			DELETE FOR workbook = tnWB IN xl_hdrfooterdefn
			DELETE FOR workbook = tnWB IN xl_hdrfootertext
			DELETE FOR workbook = tnWB IN xl_validation
			DELETE FOR workbook = tnWB IN xl_namerange
			DELETE FOR workbook = tnWB IN xl_numfmts
			DELETE FOR workbook = tnWB IN xl_rows
			DELETE FOR workbook = tnWB IN xl_ndxcolors
			DELETE FOR workbook = tnWB IN xl_mrucolors
			DELETE FOR workbook = tnWB IN xl_relationships
			DELETE FOR workbook = tnWB IN xl_extsheets
			DELETE FOR workbook = tnWB IN xl_extcells
			
			lnNdx = ASCAN(this.LastBorderId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastBorderId, lnNdx)
				lnNdx = ALEN(this.LastBorderId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastBorderId[lnNdx, 2]
				ELSE
					this.LastBorderId[1, 1] = 0                                          && Workbook Id
					this.LastBorderId[1, 2] = 0                                          && Last Border Id
				ENDIF
			ENDIF
			
			lnNdx = ASCAN(this.LastCellXfsId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastCellXfsId, lnNdx)
				lnNdx = ALEN(this.LastCellXfsId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastCellXfsId[lnNdx, 2]
				ELSE
					this.LastCellXfsId[1, 1] = 0                                         && Workbook Id
					this.LastCellXfsId[1, 2] = 0                                         && Last CellXfs Id
				ENDIF
			ENDIF
			
			lnNdx = ASCAN(this.LastFillId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastFillId, lnNdx)
				lnNdx = ALEN(this.LastFillId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastFillId[lnNdx, 2]
				ELSE
					this.LastFillId[1, 1] = 0                                            && Workbook Id
					this.LastFillId[1, 2] = 0                                            && Last Fill Id
				ENDIF
			ENDIF
		
			lnNdx = ASCAN(this.LastFontId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastFontId, lnNdx)
				lnNdx = ALEN(this.LastFontId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastFontId[lnNdx, 2]
				ELSE
					this.LastFontId[1, 1] = 0                                            && Workbook Id
					this.LastFontId[1, 2] = 0                                            && Last Font Id
				ENDIF
			ENDIF
		
			lnNdx = ASCAN(this.LastIndexColorId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastIndexColorId, lnNdx)
				lnNdx = ALEN(this.LastIndexColorId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastIndexColorId[lnNdx, 2]
				ELSE
					this.LastIndexColorId[1, 1] = 0                                      && Workbook Id
					this.LastIndexColorId[1, 2] = 0                                      && Last IndexColor Id
				ENDIF
			ENDIF
			
			lnNdx = ASCAN(this.LastMruColorId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastMruColorId, lnNdx)
				lnNdx = ALEN(this.LastMruColorId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastMruColorId[lnNdx, 2]
				ELSE
					this.LastMruColorId[1, 1] = 0                                        && Workbook Id
					this.LastMruColorId[1, 2] = 0                                        && Last mruColor Id
				ENDIF
			ENDIF
		
			lnNdx = ASCAN(this.LastNumFmtId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastNumFmtId, lnNdx)
				lnNdx = ALEN(this.LastNumFmtId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastNumFmtId[lnNdx, 2]
				ELSE
					this.LastNumFmtId[1, 1] = 0                                          && Workbook Id
					this.LastNumFmtId[1, 2] = 0                                          && Last NumFmt Id
				ENDIF
			ENDIF
		
			lnNdx = ASCAN(this.LastSheetId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastSheetId, lnNdx)
				lnNdx = ALEN(this.LastSheetId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastSheetId[lnNdx, 2]
				ELSE
					this.LastSheetId[1, 1] = 0                                           && Workbook Id
					this.LastSheetId[1, 2] = 0                                           && Last Sheet Id
				ENDIF
			ENDIF
		
			lnNdx = ASCAN(this.LastStringId, tnWB, 1, -1, 1, 15)
			IF lnNdx > 0
				ADEL(this.LastStringId, lnNdx)
				lnNdx = ALEN(this.LastStringId, 1) - 1
				IF lnNdx > 0
					DIMENSION this.LastStringId[lnNdx, 2]
				ELSE
					this.LastStringId[1, 1] = 0                                          && Workbook Id
					this.LastStringId[1, 2] = 0                                          && Last String Id
				ENDIF
			ENDIF
			llSuccess = True
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
		ENDTRY
		RETURN llSuccess
	ENDPROC

	PROCEDURE Demo		&& Demo for the features of this class
		LOCAL lnWb, lnSh1, lnRow, lnCol, lnSh2, lnSh3, lnSh4, lnCnt, lnFormat, llBoolean, lcNamedRange
		LOCAL lnSh7, lnSh5, loInline, lnStyle1, lnStyle2, lnBorders, lcString, loProtection, lnCellXfsId, lnStyle3
		LOCAL lnStyle4, lnStyle
		lnWb = this.CreateWorkbook("ExcelTest.xlsx")
		IF lnWb = 0
			RETURN False
		ENDIF
		
		lnSh1 = this.AddSheet(lnWb, "Test Sheet 1")
		IF lnSh1 = 0
			RETURN False
		ENDIF
		this.FreezePanes(lnWb, lnSh1, 1, 2)
		
		*-*	Add values to cells
		llBoolean = True
		FOR lnRow=1 TO 10
			FOR lnCol=1 TO 9
				this.SetCellValue(lnWb, lnSh1, lnRow, lnCol, SYS(2015))
			ENDFOR
			llBoolean = !llBoolean
			this.SetCellValue(lnWb, lnSh1, lnRow, 10, llBoolean)
		ENDFOR
		
		*-*	Add data grouping by rows
		this.AddGroupByRow(lnWb, lnSh1, 4, 8)
		
		*-*	Set row height
		*this.SetRowHeight(lnWb, lnSh1, 6, 25)
		this.AutoHeight = True
		
		*-*	Example of cell styles
		lnStyle1  = this.CreateFormatStyle(lnWB)                             && Create the base style definition
		lnBorders = BORDER_LEFT + BORDER_RIGHT + BORDER_TOP + BORDER_BOTTOM
		this.AddStyleBorders(lnWB, lnStyle1, lnBorders, BORDER_STYLE_THIN, RGB(16,100,200))
		this.AddStyleFont(lnWB, lnStyle1, "Times New Roman", 14, False, False, RGB(0,0,255))
		this.SetCellStyleRange(lnWB, lnSh1, 2, 1, 2, 9, lnStyle1)
		
		lnStyle2  = this.CreateFormatStyle(lnWB)                             && Create the base style definition
		lnBorders = BORDER_BOTTOM
		this.AddStyleBorders(lnWB, lnStyle2, lnBorders, BORDER_STYLE_THICK, RGB(16,100,200))
		this.AddStyleFont(lnWB, lnStyle2, "Arial", 16, True, False)
		this.SetCellStyleRange(lnWB, lnSh1, 4, 1, 4, 9, lnStyle2)
		
		*-*	* Indentation examples  [OLD WAY - DEPRECATED METHODS]
		*-*	this.SetCellIndent(lnWb, lnSh1, 2, 6, 3)
		*-*	this.SetCellIndent(lnWb, lnSh1, 4, 6, 3)
		*-*	this.SetCellIndent(lnWb, lnSh1, 6, 6, 3)
		*-*	this.SetCellIndent(lnWb, lnSh1, 8, 6, 3)
		*-*	this.SetCellIndent(lnWb, lnSh1, 10, 6, 3)
		*-*
		*-*	*                tnWB, tnSheet, tnCellRow, tnCellCol,           tcFName, tnFSize, tlBold, tlItalic,     tnFColor,  tcULine, tlStrikThr, tcVPos
		*-*	this.SetCellFont(lnWb, lnSh1,           1,         1, "Arial",                14,   True,     True, RGB(0,0,255))
		*-*	this.SetCellFont(lnWb, lnSh1,           2,         1, "Times New Roman",        ,       ,         , RGB(0,0,255))
		*-*	this.SetCellFont(lnWb, lnSh1,           3,         1,                  ,      14,   True)
		*-*	this.SetCellFont(lnWb, lnSh1,           4,         1, "Arial",                14,   True,     True, RGB(0,0,255))
		*-*	this.SetCellFont(lnWb, lnSh1,           6,         1,                  ,        ,       ,         ,             , 'single')
		*-*	this.SetCellFont(lnWb, lnSh1,           7,         1,                  ,        ,       ,         ,             , 'double')
		*-*	this.SetCellFont(lnWb, lnSh1,           8,         1,                  ,        ,       ,         ,             ,         , True)
		*-*	this.SetCellFont(lnWb, lnSh1,           9,         1,                  ,        ,       ,         ,             ,         ,            , "superscript")
		*-*
		*-*	* New procedure each line is drawed with the same style and color
		*-*	this.SetCellBorder(lnWb, lnSh1, 3, 4, 63, BORDER_STYLE_THIN,   RGB(16,100,200))
		*-*	this.SetCellBorder(lnWb, lnSh1, 3, 6, 31, BORDER_STYLE_THICK,  RGB(100,150,200))
		*-*	this.SetCellBorder(lnWb, lnSh1, 3, 8, 47, BORDER_STYLE_DOUBLE, RGB(200,150,100))
		*-*	this.SetCellBorder(lnWb, lnSh1, 3, 4,  8, BORDER_STYLE_THIN,   RGB(16,100,200))
		*-*
		*-*	this.SetCellWordWrapRange(lnWb, lnSh1, 1, 1, 10, 9, True)
		*-*
		*-*	* Old procedure - each line can have a different style or color
		*-*	this.SetCellBorderEx(lnWb, lnSh1, 5, 2, 'thin',,'thin',,'thick',,'thick')
		*-*	this.SetCellBorderEx(lnWb, lnSh1, 9, 2, ,,,,'double',255,'thick',RGB(255,0,255))
		
		* Example of setting inline text formatting
		loInline = this.CreateInLineFormatText(lnWb, "This is an in-line formatted text string")
		loInline = this.AddInLineFontObject(loInline, 1, 4, "Arial Black", 16)
		loInline = this.AddInLineFontObject(loInline, 5, 15, "Times New Roman", 14, RGB(255,0,0), .T.)
		loInline = this.AddInLineFontObject(loInline, 20, 21, "Arial", 12)
		this.SetCellInLineFormatText(lnWb, lnSh1, 16, 1, loInline)
		
		* Rotated text
		this.SetCellValue(lnWb, lnSh1, 17, 1, "This text is rotated 45 degrees")
		this.SetCellTextRotation(lnWb, lnSh1, 17, 1, 45)
		this.SetCellValue(lnWb, lnSh1, 18, 1, "This text is rotated -45 degrees")
		this.SetCellTextRotation(lnWb, lnSh1, 18, 1, -45)
		
		* Similar text
		this.SetCellValue(lnWb, lnSh1, 15, 7, "This text is similar")
		this.SetCellValue(lnWb, lnSh1, 16, 7, "This text is similar to the one above but is longer length")
		
		* Text with ASCII > 128
		this.SetCellValue(lnWb, lnSh1, 19, 1, "This text that has ASCII chars > 128:" + CRLF + "Trademark symbol: " + CHR(174) + CRLF + "Copyright symbol: " + CHR(169))
		
		* Column Width examples
		this.SetColumnWidth(lnWb, lnSh1, 1, 25)
		this.SetColumnWidth(lnWb, lnSh1, 2, 15)
		this.SetColumnWidth(lnWb, lnSh1, 3, 15)
		this.SetColumnWidth(lnWb, lnSh1, 4, 20)
		this.SetColumnWidth(lnWb, lnSh1, 5, 15)
		this.SetColumnWidth(lnWb, lnSh1, 6, 25)
		this.SetColumnWidth(lnWb, lnSh1, 9, 15)
		
		*Row inserted
		this.InsertRow(lnWb, lnSh1, 5, INSERT_AFTER)
		
		*-*	Add another data grouping by rows (level 2)
		this.AddGroupByRow(lnWb, lnSh1, 6, 8)
		
		*Column inserted
		this.InsertColumn(lnWb, lnSh1, 5, INSERT_RIGHT)
		
		* Cell insert
		this.InsertCell(lnWb, lnSh1,  9, 9, INSERT_RIGHT)
		this.InsertCell(lnWb, lnSh1, 11, 9, INSERT_BEFORE)
		
		* Sheet setup examples
		this.SetPrintOrientation(lnWb, lnSh1, PORTRAIT_PRINT_ORIENTATION)
		this.SetPaperSize(lnWb, lnSh1, PAPERSIZE_LEGAL)
		this.SetSheetScale(lnWb, lnSh1, 50)
		
		* Image example (1st))
		lcImageFile = ADDBS(SYS(5) + SYS(2003)) + "vfpxbanner.gif"                         && Must provide full path
		this.AddImage(lnWb, lnSh1, lcImageFile, IMAGE_ANCHOR_TYPE_ONE,, 3, 0, 30, 0)       && Positioned C30 cell
		
		
		*******************************************************************************
		
		lnSh2 = this.AddSheet(lnWb, "Test Sheet 2")
		IF lnSh2 = 0
			RETURN False
		ENDIF
		this.FreezePanes(lnWb, lnSh2, 1)
		
		*-*	Add column filtering
		this.AddAutoFilter(lnWb, lnSh2, 2, 5)
		this.AddColumnFilter(lnWb, lnSh2, 4, FILTER_OP_GREATERTHAN, 16, True)
		this.AddColumnFilter(lnWb, lnSh2, 4, FILTER_OP_LESSTHAN, 24, True)
		
		this.SetTabColor(lnWb, lnSh2, RGB(51,102,255))
		
		this.SetCellValue(lnWb, lnSh2, 1, 1, "Column Header 1")
		this.SetCellValue(lnWb, lnSh2, 1, 2, "Column Header 2")
		this.SetCellValue(lnWb, lnSh2, 1, 3, "Column Header 3")
		this.SetCellValue(lnWb, lnSh2, 1, 4, "Column Header 4")
		this.SetCellValue(lnWb, lnSh2, 1, 5, "Column Header 5")
		
		this.SetCellFont(lnWb, lnSh2, 1, 1, "Arial", 14, True, False, RGB(255,255,255))
		this.SetCellFill(lnWb, lnSh2, 1, 1, RGB(51,102,255))
		this.CellFormatPainter(lnWb, lnSh2, 1, 1, 1, 2, 1, 5)
		
		this.SetColumnWidthRange(lnWb, lnSh2, 1, 5, 24)
		
		* Add integer values to cells
		FOR lnRow=2 TO 6
			FOR lnCol=1 TO 5
				this.SetCellValue(lnWb, lnSh2, lnRow, lnCol, lnRow*lnCol)
			ENDFOR
		ENDFOR
		
		* Add SUM() formula to columns
		FOR lnCol=1 TO 5
			lcColumn = this.ColumnIndexToAscii(lnCol)
			this.SetCellFormula(lnWb, lnSh2, 7, lnCol, "=SUM(" + lcColumn + "2:" + lcColumn + "6)")
		ENDFOR
		
		* Add floating point values to cells
		FOR lnRow=10 TO 15
			FOR lnCol=3 TO 5
				this.SetCellValue(lnWb, lnSh2, lnRow, lnCol, lnRow*1.25)
			ENDFOR
		ENDFOR
		this.SetRowHidden(lnWb, lnSh2, 13, True)
		this.SetRowHidden(lnWb, lnSh2, 14, True)
		
		* Add AVERAGE(), MAX(), & MIN() formula to columns
		this.SetCellFormula(lnWb, lnSh2, 16, 3, "=AVERAGE(C10:C15)")
		this.SetCellFormula(lnWb, lnSh2, 16, 4, "=MAX(D10:D15)")
		this.SetCellFormula(lnWb, lnSh2, 16, 5, "=MIN(E10:E15)")
		
		this.SetCellValue(lnWb, lnSh2, 16, 1, "This row contains calculated formulas -->")
		
		*-*	Test to over-write an existing formula with a cell value
		*this.SetCellValue(lnWb, lnSh2, 16, 5, "This value overwrote formula value")
		
		* Merge some cells
		this.MergeCells(lnWb, lnSh2, 2, 8, 2, 10)
		this.MergeCells(lnWb, lnSh2, 3, 8, 3, 10)
		this.MergeCells(lnWb, lnSh2, 5, 8, 10, 10)
		this.SetCellValue(lnWb, lnSh2, 2, 8, "Merged H2:J2")
		this.SetCellValue(lnWb, lnSh2, 3, 8, "Merged H3:J3")
		this.SetCellValue(lnWb, lnSh2, 5, 8, "Merged H5:J10")
		this.SetCustomPaperSize(lnWb, lnSh2, 841, 1189, "mm")
		this.SetHeaderFooterSetup(lnWb, lnSh2, False, True, False, False)
		this.SetHeaderFooterText(lnWb, lnSh2, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_HDR_CENTER, "This is First Page", "Arial", 16, HEADERFOOTER_FONT_STYLE_BOLD, RGB(255,0,0))
		this.SetHeaderFooterText(lnWb, lnSh2, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_HDR_CENTER, "These are Additional Pages", "Arial", 10, HEADERFOOTER_FONT_STYLE_BOLD, RGB(0,128,0))
		this.SetHeaderFooterText(lnWb, lnSh2, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_FTR_CENTER, "Page &amp;P of &amp;N", "Arial", 16, HEADERFOOTER_FONT_STYLE_BOLD)
		
		* Hyperlink example - set in Sheet 1 (must have sheet 2 created first)
		this.SetCellValue(lnWb, lnSh1, 26, 1, "Hyperlink Example 1 (Sheet)")
		this.AddHyperLinkSheet(lnWb, lnSh1, 26, 1, 26, 1, lnSh2, 1, 1, 1, 3)          && Sets the display value as the cell value by default
		this.SetCellValue(lnWb, lnSh1, 27, 1, "Hyperlink Example 2 (External file)")
		this.AddHyperLinkFile(lnWb, lnSh1, 27, 1, 27, 1, "..\vfpxworkbookxlsx.vcx")   && The target file must exist; give path to file (can be relative)
		
		*******************************************************************************
		
		lnSh3 = this.AddSheet(lnWb, "Cell Formats")
		IF lnSh3 = 0
			RETURN False
		ENDIF
		
		this.DateTimeSeparator = "-"   && Change this value to set the separator for date-time values; default value is /
		
		lnFormat = this.AddCustomNumericFormat(lnWb, '[Green]"$"#,##0.00', '[Red]"$"#,##0.00')
		this.SetCellValue(lnWb, lnSh3, 1, 1, 56.558)
		this.SetCellValue(lnWb, lnSh3, 1, 2, "Custom defined format: " + this.GetCustomNumericFormat(lnWb, lnFormat))
		this.SetCellNumberFormat(lnWb, lnSh3, 1, 1, lnFormat)
		
		this.SetColumnWidth(lnWb, lnSh3, 1, 30)
		this.SetColumnWidth(lnWb, lnSh3, 2, 110)
		this.SetColumnWidth(lnWb, lnSh3, 3, 30)
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_INTEGER)
		this.SetCellValue(lnWb, lnSh3, 3, 1, 16898)
		this.SetCellStyle(lnWb, lnSh3, 3, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 3, 2, "CELL_FORMAT_INTEGER - Integer format")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_INTEGER)
		this.SetCellValue(lnWb, lnSh3, 4, 1, 16898)
		this.SetCellStyle(lnWb, lnSh3, 4, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 4, 2, "CELL_FORMAT_COMMA_INTEGER - Integer format, comma separated")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_FLOAT)
		this.SetCellValue(lnWb, lnSh3, 5, 1, 13355.2)
		this.SetCellStyle(lnWb, lnSh3, 5, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 5, 2, "CELL_FORMAT_FLOAT - Float format")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_FLOAT)
		this.SetCellValue(lnWb, lnSh3, 6, 1, 13355.2)
		this.SetCellStyle(lnWb, lnSh3, 6, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 6, 2, "CELL_FORMAT_COMMA_FLOAT - Float format, comma separated")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_CURRENCY_PAREN)
		this.SetCellValue(lnWb, lnSh3, 7, 1, -13355.2)
		this.SetCellStyle(lnWb, lnSh3, 7, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 7, 2, "CELL_FORMAT_CURRENCY_PAREN - Currency format, negative in parenthesis")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_CURRENCY_RED_PAREN)
		this.SetCellValue(lnWb, lnSh3, 8, 1, -13355.2)
		this.SetCellStyle(lnWb, lnSh3, 8, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 8, 2, "CELL_FORMAT_CURRENCY_RED_PAREN - Currency format, negative red in parenthesis")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_PERCENT_INTEGER)
		this.SetCellValue(lnWb, lnSh3, 10, 1, 0.55)
		this.SetCellStyle(lnWb, lnSh3, 10, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 10, 2, "CELL_FORMAT_PERCENT_INTEGER - Percent format, integer")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_PERCENT_FLOAT)
		this.SetCellValue(lnWb, lnSh3, 11, 1, 0.5534)
		this.SetCellStyle(lnWb, lnSh3, 11, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 11, 2, "CELL_FORMAT_PERCENT_FLOAT - Percent format, float")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_EXPONENT)
		this.SetCellValue(lnWb, lnSh3, 13, 1, 456800000.00)
		this.SetCellStyle(lnWb, lnSh3, 13, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 13, 2, "CELL_FORMAT_EXPONENT - Exponential format")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_FRACTION_1)
		this.SetCellValue(lnWb, lnSh3, 15, 1, 0.25)
		this.SetCellStyle(lnWb, lnSh3, 15, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 15, 2, "CELL_FORMAT_FRACTION_1 - Fraction format, single place")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_FRACTION_2)
		this.SetCellValue(lnWb, lnSh3, 16, 1, 0.3125)
		this.SetCellStyle(lnWb, lnSh3, 16, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 16, 2, "CELL_FORMAT_FRACTION_2 - Fraction format, 2 places")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATE_DMMMYY)
		this.SetCellValue(lnWb, lnSh3, 18, 1, DATE())
		this.SetCellStyle(lnWb, lnSh3, 18, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 18, 2, "CELL_FORMAT_DATE_DMMMYY - Default date format")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATE_DMMMYY)
		this.SetCellValue(lnWb, lnSh3, 19, 1, DATE())
		this.SetCellStyle(lnWb, lnSh3, 19, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 19, 2, "CELL_FORMAT_DATE_DMMMYY - Date format, d-mmm-yy")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATE_DMMM)
		this.SetCellValue(lnWb, lnSh3, 20, 1, DATE())
		this.SetCellStyle(lnWb, lnSh3, 20, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 20, 2, "CELL_FORMAT_DATE_DMMM - Date format, d-mmm")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATE_MMMYY)
		this.SetCellValue(lnWb, lnSh3, 21, 1, DATE())
		this.SetCellStyle(lnWb, lnSh3, 21, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 21, 2, "CELL_FORMAT_DATE_MMMYY - Date format, mmm-yy")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_HMMAMPM)
		this.SetCellValue(lnWb, lnSh3, 23, 1, 0.35733796296296294)
		this.SetCellStyle(lnWb, lnSh3, 23, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 23, 2, "CELL_FORMAT_TIME_HMMAMPM - Time format, h:mm AM/PM")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_HMMSSAMPM)
		this.SetCellValue(lnWb, lnSh3, 24, 1, 0.35733796296296294)
		this.SetCellStyle(lnWb, lnSh3, 24, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 24, 2, "CELL_FORMAT_TIME_HMMSSAMPM - Time format, h:mm:ss AM/PM")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_HMM)
		this.SetCellValue(lnWb, lnSh3, 25, 1, 0.35733796296296294)
		this.SetCellStyle(lnWb, lnSh3, 25, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 25, 2, "CELL_FORMAT_TIME_HMM - Time format, h:mm")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_HMMSS)
		this.SetCellValue(lnWb, lnSh3, 26, 1, 0.35733796296296294)
		this.SetCellStyle(lnWb, lnSh3, 26, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 26, 2, "CELL_FORMAT_TIME_HMMSS - Time format, h:mm:ss")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_MDYYHMM)
		this.SetCellValue(lnWb, lnSh3, 28, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 28, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 28, 2, "CELL_FORMAT_DATETIME_MDYYHMM - Date-Time format, m/d/yy h:mm")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_DDMMMYYYY_TTAM)
		this.SetCellValue(lnWb, lnSh3, 29, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 29, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 29, 2, "CELL_FORMAT_DATETIME_DDMMMYYYY_TTAM - Date-Time format, [$-409]dd/mmm/yyyy\ h:mm\ AM/PM;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_DDMMMYYYY_TT24)
		this.SetCellValue(lnWb, lnSh3, 30, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 30, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 30, 2, "CELL_FORMAT_DATETIME_DDMMMYYYY_TT24 - Date-Time format, dd/mmm/yyyy\ h:mm;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_MMMDDYYYY_TTAM)
		this.SetCellValue(lnWb, lnSh3, 31, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 31, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 31, 2, "CELL_FORMAT_DATETIME_MMMDDYYYY_TTAM - Date-Time format, [$-409]mmm\ d\,\ yyyy\ h:mm\ AM/PM;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_MMMDDYYYY_TT24)
		this.SetCellValue(lnWb, lnSh3, 32, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 32, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 32, 2, "CELL_FORMAT_DATETIME_MMMDDYYYY_TT24 - Date-Time format, [$-409]mmm\ d\,\ yyyy\ h:mm;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_MDYY_TTAM)
		this.SetCellValue(lnWb, lnSh3, 33, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 33, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 33, 2, "CELL_FORMAT_DATETIME_MDYY_TTAM - Date-Time format, m/d/yy\ h:mm\ AM/PM;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_DATETIME_MDYY_TT24)
		this.SetCellValue(lnWb, lnSh3, 34, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 34, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 34, 2, "CELL_FORMAT_DATETIME_MDYY_TT24 - Date-Time format, m/d/yy\ h:mm;@")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_INTEGER_PAREN)
		this.SetCellValue(lnWb, lnSh3, 36, 1, -89235)
		this.SetCellStyle(lnWb, lnSh3, 36, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 36, 2, "CELL_FORMAT_COMMA_INTEGER_PAREN - Numeric format, #,##0;(#,##0)")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_INTEGER_RED_PAREN)
		this.SetCellValue(lnWb, lnSh3, 37, 1, -12457)
		this.SetCellStyle(lnWb, lnSh3, 37, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 37, 2, "CELL_FORMAT_COMMA_INTEGER_RED_PAREN - Numeric format, #,##0;[Red](#,##0)")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_FLOAT_PAREN)
		this.SetCellValue(lnWb, lnSh3, 38, 1, -1245789.22)
		this.SetCellStyle(lnWb, lnSh3, 38, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 38, 2, "CELL_FORMAT_COMMA_FLOAT_PAREN - Numeric format, #,##0.00;(#,##0.00)")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_COMMA_FLOAT_RED_PAREN)
		this.SetCellValue(lnWb, lnSh3, 39, 1, -125896.33)
		this.SetCellStyle(lnWb, lnSh3, 39, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 39, 2, "CELL_FORMAT_COMMA_FLOAT_RED_PAREN - Numeric format, #,##0.00;[Red](#,##0.00)")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_MMSS)
		this.SetCellValue(lnWb, lnSh3, 41, 1, 6.25E-2)
		this.SetCellStyle(lnWb, lnSh3, 41, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 41, 2, "CELL_FORMAT_TIME_MMSS - Time format")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_TIME_H_MMSS)
		this.SetCellValue(lnWb, lnSh3, 42, 1, 0.35733796296296294)
		this.SetCellStyle(lnWb, lnSh3, 42, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 42, 2, "CELL_FORMAT_TIME_H_MMSS - Time format, [h]:mm:ss")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_ACC_CURR_US_PAREN)
		this.SetCellValue(lnWb, lnSh3, 44, 1, -445675.55)
		this.SetCellStyle(lnWb, lnSh3, 44, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 44, 2, "CELL_FORMAT_TIME_H_MMSS - CELL_FORMAT_ACC_CURR_US_PAREN")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_CURRENCY_RED)
		this.SetCellValue(lnWb, lnSh3, 45, 1, -45.55)
		this.SetCellStyle(lnWb, lnSh3, 45, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 45, 2, "CELL_FORMAT_CURRENCY_RED")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_ACC_CURR_POUNDS)
		this.SetCellValue(lnWb, lnSh3, 46, 1, -45.55)
		this.SetCellStyle(lnWb, lnSh3, 46, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 46, 2, "CELL_FORMAT_ACC_CURR_POUNDS")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_ACC_CURR_EURO)
		this.SetCellValue(lnWb, lnSh3, 47, 1, -45.55)
		this.SetCellStyle(lnWb, lnSh3, 47, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 47, 2, "CELL_FORMAT_ACC_CURR_EURO")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_CURR_EURO_RED)
		this.SetCellValue(lnWb, lnSh3, 48, 1, -45.55)
		this.SetCellStyle(lnWb, lnSh3, 48, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 48, 2, "CELL_FORMAT_CURR_EURO_RED")
		
		lnStyle = this.CreateFormatStyle(lnWb)
		this.AddStyleCellFormat(lnWb, lnStyle, CELL_FORMAT_CURR_POUNDS_RED)
		this.SetCellValue(lnWb, lnSh3, 49, 1, -45.55)
		this.SetCellStyle(lnWb, lnSh3, 49, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 49, 2, "CELL_FORMAT_CURR_POUNDS_RED")
		
		lnStyle  = this.CreateFormatStyle(lnWb)
		lnFormat = this.AddCustomDateTimeFormat(lnWb, 'dd mmm yy')
		this.AddStyleCellFormat(lnWb, lnStyle, lnFormat)
		this.SetCellValue(lnWb, lnSh3, 51, 1, DATE())
		this.SetCellStyle(lnWb, lnSh3, 51, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 51, 2, "dd mmm yy [custom defined]")
		
		lnStyle  = this.CreateFormatStyle(lnWb)
		lnFormat = this.AddCustomDateTimeFormat(lnWb, 'dd/mmm/yyyy\ h:mm;@')
		this.AddStyleCellFormat(lnWb, lnStyle, lnFormat)
		this.SetCellValue(lnWb, lnSh3, 52, 1, DATETIME())
		this.SetCellStyle(lnWb, lnSh3, 52, 1, lnStyle)
		this.SetCellValue(lnWb, lnSh3, 52, 2, "dd/mmm/yyyy\ h:mm;@ [custom defined]")
		
		this.SetPrintOrientation(lnWb, lnSh3, LANDSCAPE_PRINT_ORIENTATION)
		*-*	this.SetPrintFitToHeight(lnWb, lnSh3, 2)
		
		*******************************************************************************
		
		lnSh4 = this.AddSheet(lnWb, "Color Sheet")
		IF lnSh4 = 0
			RETURN False
		ENDIF
		
		this.SetCellValue(lnWb, lnSh4, 1, 1, "RGB(51,102,255)")
		this.SetCellFill(lnWb, lnSh4, 1, 1, RGB(51,102,255))
		
		this.SetCellValue(lnWb, lnSh4, 1, 2, "RGB(255,0,0)")
		this.SetCellFill(lnWb, lnSh4, 1, 2, RGB(255,0,0))
		
		lnStyle3 = this.CreateFormatStyle(lnWB)                                                 && Create the base style definition
		this.AddStyleFill(lnWB, lnStyle3, RGB(51,102,255), RGB(51,102,255), FILL_STYLE_SOLID)   && Fill Solid off-blue backgrond and foreground (foreground is ignored when solid)
		this.AddStyleFont(lnWB, lnStyle3, , , , , RGB(255,0,0))                                 &&  Font red foreground
		this.SetCellStyle(lnWb, lnSh4, 2, 1, lnStyle3)
		this.SetCellValue(lnWb, lnSh4, 2, 1, "Fill Using Styles")
		
		lnStyle4 = this.CreateFormatStyle(lnWB)                                                 && Create the base style definition
		this.AddStyleFill(lnWB, lnStyle4, RGB(255,0,0), RGB(255,0,0), FILL_STYLE_SOLID)         && Fill Solid Red background and foreground (foreground is ignored when solid)
		this.AddStyleFont(lnWB, lnStyle4, , , , , RGB(255,255,255))                             && Font White foreground
		this.SetCellStyle(lnWb, lnSh4, 2, 2, lnStyle4)
		this.SetCellValue(lnWb, lnSh4, 2, 2, "Fill Using Styles")
		
		this.SetCellAlignmentRange(lnWb, lnSh4, 1, 1, 1, 2, CELL_HORIZ_ALIGN_CENTER, CELL_VERT_ALIGN_CENTER)
		
		this.SetColumnWidthRange(lnWb, lnSh4, 1, 2, 18)
		this.SetRowHeight(lnWb, lnSh4, 1, 60)
		
		*******************************************************************************
		
		lnSh5 = this.AddSheet(lnWb, "Blank Sheet")
		
		*******************************************************************************
		
		lnSh6 = this.AddSheet(lnWb, "Image Example")
		IF lnSh6 > 0
			lcImageFile = ADDBS(SYS(5) + SYS(2003)) + "vfpxbanner.gif"                     && Must provide full path
			lnImgId = this.AddImage(lnWb, lnSh6, lcImageFile, IMAGE_ANCHOR_TYPE_TWO)       && Positioned by defaults  A1 cell
			IF lnImgId = 0
				WAIT WINDOW "Add Image Failed" NOWAIT
				this.DeleteSheet(lnWb, lnSh6)
			ENDIF
		ENDIF
		
		
		*******************************************************************************
		
		lnSh7 = this.AddSheet(lnWb, "List Values", HIDDEN_SHEET_STATE)
		IF lnSh7 = 0
			RETURN False
		ENDIF
		
		this.SetCellValidation(lnWb, lnSh1, 13, 1, LIST_VALID_TYPE, , , , , , , , , "Yes,No,Maybe")
		this.SetCellValue(lnWb, lnSh1, 13, 2, "The cell to the left has a droplist validation")
		lnCellXfsId = this.GetCellStyle(lnWb, lnSh1, 13, 2)
		lnCellXfsId = this.CopyStyle(lnWb, lnCellXfsId)
		this.AddStyleProtection(lnWb, lnCellXfsId, 0, 0)         && unlock the cell protected state
		this.SetCellStyle(lnWb, lnSh1, 13, 1, lnCellXfsId)
		
		this.SetCellValue(lnWb, lnSh7, 1, 1, "Named Value List")
		this.SetCellValue(lnWb, lnSh7, 2, 1, "Named Value 1")
		this.SetCellValue(lnWb, lnSh7, 3, 1, "Named Value 2")
		this.SetCellValue(lnWb, lnSh7, 4, 1, "Named Value 3")
		this.SetCellValue(lnWb, lnSh7, 5, 1, "Named Value 4")
		this.SetCellValue(lnWb, lnSh7, 6, 1, "Named Value 5")
		this.SetCellValue(lnWb, lnSh7, 7, 1, "Named Value 6")
		
		lcNamedRange = this.AddNamedRange(lnWb, lnSh7, "Named Range", SCOPE_WB_NAMED_RANGE, "This is a comment", 2, 1, 7, 1)
		
		this.SetCellValidation(lnWb, lnSh1, 14, 1, LIST_VALID_TYPE, , , , , , , , , "=" + lcNamedRange)
		this.SetCellValue(lnWb, lnSh1, 14, 2, "The cell to the left has a droplist validation based on a Named Range (stored in Hidden Sheet6)")
		lnCellXfsId = this.GetCellStyle(lnWb, lnSh1, 14, 1)
		lnCellXfsId = this.CopyStyle(lnWb, lnCellXfsId)
		this.AddStyleProtection(lnWb, lnCellXfsId, 0, 0)         && unlock the cell protected state
		this.SetCellStyle(lnWb, lnSh1, 14, 1, lnCellXfsId)
		
		*-*  For testing sheet saving only
		*this.DeleteSheet(lnWb, lnSh2)
		
		*-*	Set sheet protection on for sheet1
		*loProtection = this.GetSheetProtection(lnWb, lnSh1)      && use this method to get the protection object structure to be passed to SetSheetProtection()
		*loProtection.Locked = True
		*this.SetSheetProtection(lnWb, lnSh1, loProtection)
		*this.SetSheetProtection(lnWb, lnSh2, loProtection)      && If set then the filter is not usable...
		
		*-*	Protect the workbook
		*this.SetWorkbookProtection(lnWb, True)
		
		*-*	Save the workbook
		this.SaveWorkbook(lnWb)
	ENDPROC

	PROTECTED PROCEDURE DeriveSheetName		&& Derives the sheet name
		LPARAMETERS txWB
		DO CASE
			CASE VARTYPE(txWB) = "C"
				lcSheetName = JUSTSTEM(txWB)
		
			CASE VARTYPE(txWB) = "N"
				lcSheetName = JUSTSTEM(this.GetWorkbookFileName(txWB))
		
			OTHERWISE
				RETURN ""
		ENDCASE
		RETURN lcSheetName
	ENDPROC

	PROTECTED PROCEDURE Destroy
		SET DEBUGOUT TO
		this.DestroyWorkingCursors()
		this.OnDestroy()
	ENDPROC

	PROTECTED PROCEDURE DestroyWorkingCursors		&& Destroys the working cursors
		LOCAL loException
		TRY
			USE IN SELECT('xl_strings')
			USE IN SELECT('xl_workbooks')
			USE IN SELECT('xl_sheets')
			USE IN SELECT('xl_cells')
			USE IN SELECT('xl_fonts')
			USE IN SELECT('xl_fills')
			USE IN SELECT('xl_borders')
			USE IN SELECT('xl_mergecells')
			USE IN SELECT('xl_columns')
			USE IN SELECT('xl_hdrfooterdefn')
			USE IN SELECT('xl_hdrfootertext')
			USE IN SELECT('xl_numfmts')
			USE IN SELECT('xl_namerange')
			USE IN SELECT('xl_validation')
			USE IN SELECT('xl_strformat')
			USE IN SELECT('xl_cellxfs')
			USE IN SELECT('xl_rows')
			USE IN SELECT('xl_ndxcolors')
			USE IN SELECT('xl_mrucolors')
			USE IN SELECT('xl_relationships')
			USE IN SELECT('xl_extsheets')
			USE IN SELECT('xl_extcells')
			USE IN SELECT('xl_hyperlinks')
			USE IN SELECT('xl_contenttypes')
			USE IN SELECT('xl_images')
			USE IN SELECT('xl_shapes')
			USE IN SELECT('xl_shptext')
			USE IN SELECT('xl_filters')
			USE IN SELECT('xl_formulas')
		
		CATCH TO loException
		ENDTRY
	ENDPROC

	PROTECTED PROCEDURE Documentation		&& Documentation of the methods and properties for this class
		*-*
		*-* Written by:  Vilhelm Praisach
		*-*              Gregory Green
		*-*
		*-* Release History:
		*-*
		*-*  May 11, 2015     Initial release.
		*-*  May 12, 2015     Added new methods: SetCellFormatRange, SetCellBorderRange, SaveGridToWorkbook
		*-*                   Added new properties: Title, Subject, CompanyName
		*-*                   Added check in SetCellValue that if a value is being set in a merged cell range then the value is set in the upper
		*-*                       left cell of the merged range
		*-*                   Corrected sheet references when writing the sheet XML
		*-*  May 27, 2015     Added new methods: ConvertPixelsToExcelUnits, GetWorkbook, GetWorkbookSheets, DeleteWorkbook, DeleteAllWorkbooks,
		*-*                                      SetCellAlignment, SetCellNumberDecimals, SetCellNumberFormat, SetCellNumberFormatRange,
		*-*                                      SetColumnWidthRange, SetRowHeight, SetRowHeightRange, SetCellWordWrap
		*-*                   Renamed method RemoveSheet to DeleteSheet
		*-*                   Corrected string to XML conversion (was performing a double conversion of the string)
		*-*                   Corrected issue of generated XLSX file being deleted; increase of Sleep() time for file rename
		*-* June 1, 2015      Corrected bug in merge cell and cell value assignment (after a merge cell was performed any subsequent sheets
		*-*                        added that had cells in the same merge range with value assignments was not having the values set correctly)
		*-*                   Changed property name ExcelXlsxVersion to ExcelXlsxRelease; this property will give the Release number (integer)
		*-*                        of this class
		*-* June 5, 2015      Added to set the default selected table/cursor (value retuned by ALIAS() function) when the default table/cursor
		*-*                        is changed in a method
		*-*                   Added new method: SaveTableToWorkbook
		*-* June 8, 2015      No new features.  Code was optimized for better performance.
		*-*
		*-*                   The first optimization I changed the SetCellValue() code to now always attempt to INSERT first. I added the CANDIDATE
		*-*                   clause to the primary INDEX so that if a cell is already entered, the INSERT command will fail in a TRY-CATCH block
		*-*                   (duplicate KEY); in the CATCH portion I do an update to the cell value. This reduced the time to assign the values
		*-*                   significantly. I had been doing a SEEK() for the cell and updating if found otherwise doing an INSERT
		*-*
		*-*                   The second optimization was how I was assigning a string index value. The original code did a SELECT-SQL WITH a MAX()
		*-*                   for the last used index value IN the workbook (strings are universal TO a workbook); I changed to a property value
		*-*                   (array indexed by the workbook number) and then just increment this value as it is used. Great amount of savings here too.
		*-*
		*-*                   The LAST optimization was in how the Sheet.xml were being written. I now just do a SCAN across the cells and add to the
		*-*                   XML. I was doing several SQL calls to first get the rows and then SQL calls TO the columns for each row. Again not as
		*-*                   efficient as just SCAN-ENDSCAN on the cells cursor table. Had to build in logic to populate rows that did not have a
		*-*                   cell value assigned but had row height assigned. The SQL coding was easier for this but at a great cost in time.
		*-* June 9, 2015      No new features.  Bug fix for a nasty crash of Excel when the print or print-preview is selected.  Congratulations to
		*-*                   Vilhelm for finding this bug.
		*-* July 1, 2015      Corrected return value for AddNumericFormat.
		*-*                   Changed the parameters on AddNumericFormat(); now just pass the numeric format
		*-*
		*-* Oct 29, 2015      Added new functions for opening an existing workbook (xlsx).  Added sheet print formatting methods.
		*-*
		*-************************************************************************************************************************************
	ENDPROC

	PROTECTED PROCEDURE FastExtract		&& Fast String Replacement for STREXTRACT() function
		LPARAMETERS toString, tcBegDelimiter, tcEndDelimiter, tnOccurrence
		LOCAL lnBeg, lcVal, lnEnd, lnBegLen, lnEndLen, lnOccurrence
		DO CASE
			CASE PCOUNT() = 1
				RETURN ""
		
			CASE PCOUNT() = 2
				tcEndDelimiter = tcBegDelimiter
				tnOccurrence   = 1
		
			CASE PCOUNT() = 3
				tnOccurrence = 1
		ENDCASE
		lnBegLen     = LEN(tcBegDelimiter)
		lnEndLen     = LEN(tcEndDelimiter)
		lnOccurrence = 0
		lnBeg = 1
		DO WHILE lnBeg < toString.Length
			lcVal = SYS(2600, toString.BaseAdr-1 + lnBeg, lnBegLen)
			IF lcVal == tcBegDelimiter
				FOR lnEnd=lnBeg+lnBegLen TO toString.Length
					lcVal = SYS(2600, toString.BaseAdr-1 + lnEnd, lnEndLen)
					IF lcVal == tcEndDelimiter
						lnOccurrence = lnOccurrence + 1
						IF lnOccurrence = tnOccurrence
							RETURN SYS(2600, toString.BaseAdr-1 + lnBeg, lnEnd - lnBeg + lnEndLen + 1)
						ENDIF
						lnBeg = lnEnd - 1
						EXIT
					ENDIF
				ENDFOR
			ENDIF
			lnBeg = lnBeg + 1
		ENDDO
		RETURN ""
	ENDPROC

	PROCEDURE FreezePanes		&& Freezes the panes of the sheet
		LPARAMETERS tnWB, tnSheet, tnTopRowCount, tnSideColCount
		LOCAL lxReturn
		DO CASE
			CASE PCOUNT() < 3
				RETURN False
			
			CASE PCOUNT() = 3
				tnSideColCount = 0
		ENDCASE
		IF VARTYPE(tnTopRowCount) != 'N'
			tnTopRowCount = 0
		ENDIF
		IF VARTYPE(tnSideColCount) != 'N'
			tnSideColCount = 0
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.xsplit WITH tnSideColCount, ;
					xl_sheets.ysplit WITH tnTopRowCount IN xl_sheets
			lxReturn = True
		ELSE
			lxReturn = False
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetAlphaNumericSplit		&& Returns the position of the alpha and numeric split
		LPARAMETERS tcText
		LOCAL lnChr
		FOR lnChr=1 TO LEN(tcText)
			IF ISDIGIT(SUBSTR(tcText, lnChr, 1))
				RETURN lnChr
			ENDIF
		ENDFOR
		RETURN 0
	ENDPROC

	PROTECTED PROCEDURE GetBordersRecord		&& Gets the border record; returns True if found, or False in not found
		LPARAMETERS tnWB, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp
		RETURN SEEK(BINTOC(tnWB) + tcLStyle + BINTOC(tnLColor) + tcRStyle + BINTOC(tnRColor) + tcTStyle + BINTOC(tnTColor) + tcBStyle + BINTOC(tnBColor) + ;
			   		tcDStyle + BINTOC(tnDColor) + BINTOC(tnDiagDn) + BINTOC(tnDiagUp), "xl_borders", "cellborder")
	ENDPROC

	PROCEDURE GetCellAlignment		&& Returns the cell alignment
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loAlignInfo
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			loAlignInfo = CREATEOBJECT("Empty")
			ADDPROPERTY(loAlignInfo, "HorzAlign", xl_cellxfs.halign)
			ADDPROPERTY(loAlignInfo, "VertAlign", xl_cellxfs.valign)
		ELSE
			loAlignInfo = .NULL.
		ENDIF
		RETURN loAlignInfo
	ENDPROC

	PROCEDURE GetCellBorders		&& Returns the cell border info
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loBdrInfo
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs) .AND. this.GetCursorRecord("xl_borders", tnWB, xl_cellxfs.borderId)
			loBdrInfo = CREATEOBJECT("Empty")
			ADDPROPERTY(loBdrInfo, "LeftStyle",  xl_borders.lstyle)
			ADDPROPERTY(loBdrInfo, "LeftColor",  xl_borders.lcolor)
			ADDPROPERTY(loBdrInfo, "LeftTheme",  xl_borders.ltheme)
			ADDPROPERTY(loBdrInfo, "LeftTint",   xl_borders.ltint)
			ADDPROPERTY(loBdrInfo, "LeftIndex",  xl_borders.lindexed)
		
			ADDPROPERTY(loBdrInfo, "RightStyle", xl_borders.rstyle)
			ADDPROPERTY(loBdrInfo, "RightColor", xl_borders.rcolor)
			ADDPROPERTY(loBdrInfo, "RightTheme", xl_borders.rtheme)
			ADDPROPERTY(loBdrInfo, "RightTint",  xl_borders.rtint)
			ADDPROPERTY(loBdrInfo, "RightIndex", xl_borders.rindexed)
		
			ADDPROPERTY(loBdrInfo, "TopStyle",   xl_borders.tstyle)
			ADDPROPERTY(loBdrInfo, "TopColor",   xl_borders.tcolor)
			ADDPROPERTY(loBdrInfo, "TopTheme",   xl_borders.ttheme)
			ADDPROPERTY(loBdrInfo, "TopTint",    xl_borders.ttint)
			ADDPROPERTY(loBdrInfo, "TopIndex",   xl_borders.tindexed)
		
			ADDPROPERTY(loBdrInfo, "BotStyle",   xl_borders.bstyle)
			ADDPROPERTY(loBdrInfo, "BotColor",   xl_borders.bcolor)
			ADDPROPERTY(loBdrInfo, "BotTheme",   xl_borders.btheme)
			ADDPROPERTY(loBdrInfo, "BotTint",    xl_borders.btint)
			ADDPROPERTY(loBdrInfo, "BotIndex",   xl_borders.bindexed)
		
			ADDPROPERTY(loBdrInfo, "DiagStyle",  xl_borders.dstyle)
			ADDPROPERTY(loBdrInfo, "DiagColor",  xl_borders.dcolor)
			ADDPROPERTY(loBdrInfo, "DiagTheme",  xl_borders.dtheme)
			ADDPROPERTY(loBdrInfo, "DiagTint",   xl_borders.dtint)
			ADDPROPERTY(loBdrInfo, "DiagIndex",  xl_borders.dindexed)
		
			ADDPROPERTY(loBdrInfo, "DiagDn",     xl_borders.diagdn)
			ADDPROPERTY(loBdrInfo, "DiagUp",     xl_borders.diagup)
		ELSE
			loBdrInfo = .NULL.
		ENDIF
		RETURN loBdrInfo
	ENDPROC

	PROCEDURE GetCellDataType		&& Returns the data type for the selected cell
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lxReturn
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			lxReturn = xl_cells.datatype
		ELSE
			lxReturn = .NULL.
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROCEDURE GetCellFill		&& Returns the fill info for the cell
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loFillInfo
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs) .AND. this.GetCursorRecord("xl_fills", tnWB, xl_cellxfs.fillid)
			loFillInfo = CREATEOBJECT("Empty")
			ADDPROPERTY(loFillInfo, "FgColor",   xl_fills.fgcolor)
			ADDPROPERTY(loFillInfo, "BgColor",   xl_fills.bgcolor)
			ADDPROPERTY(loFillInfo, "PatType",   xl_fills.patttype)
			ADDPROPERTY(loFillInfo, "Theme",     xl_fills.theme)
			ADDPROPERTY(loFillInfo, "Tint",      xl_fills.tint)
			ADDPROPERTY(loFillInfo, "FgIndexed", xl_fills.fgindexed)
			ADDPROPERTY(loFillInfo, "BgIndexed", xl_fills.bgindexed)
		ELSE
			loFillInfo = .NULL.
		ENDIF
		RETURN loFillInfo
	ENDPROC

	PROCEDURE GetCellFont		&& Returns the cell font settings
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loFontInfo
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs) .AND. this.GetCursorRecord("xl_fonts", tnWB, xl_cellxfs.fontid)
			loFontInfo = CREATEOBJECT("Empty")
			ADDPROPERTY(loFontInfo, "FontName",        ALLTRIM(xl_fonts.fname))
			ADDPROPERTY(loFontInfo, "FontSize",        xl_fonts.fsize)
			ADDPROPERTY(loFontInfo, "FontBold",        xl_fonts.fbold)
			ADDPROPERTY(loFontInfo, "FontItalic",      xl_fonts.fitalic)
			ADDPROPERTY(loFontInfo, "ForeColor",       xl_fonts.fcolor)
			ADDPROPERTY(loFontInfo, "FontUnderline",   ALLTRIM(xl_fonts.uline))
			ADDPROPERTY(loFontInfo, "FontStrikeThr",   xl_fonts.strkthr)
			ADDPROPERTY(loFontInfo, "FontVerticalPos", ALLTRIM(xl_fonts.fvpos))
		ELSE
			loFontInfo = .NULL.
		ENDIF
		RETURN loFontInfo
	ENDPROC

	PROCEDURE GetCellFormula		&& Returns the cell formula expression
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lcFormula, lcBaseCol, lcBaseRow, lcParsedFormula, lnNdx, lcSharedFormula, lnCnt
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnCellRow)+BINTOC(tnCellCol), "xl_formulas", "cell")
			lcFormula = ALLTRIM(xl_formulas.cellformula)
			IF EMPTY(lcFormula)
				DO CASE
					CASE xl_formulas.formulatype = "shared"
						IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_formulas.si), "xl_formulas", "si")
							lcParsedFormula = ALLTRIM(xl_formulas.parsed)
							lcSharedFormula = ALLTRIM(xl_formulas.cellformula)
							DO CASE
								CASE xl_formulas.formrel = SHARED_FORMULA_COLUMN
									IF EMPTY(lcParsedFormula)
										lnCnt     = 1
										lcBaseCol = this.ColumnIndexToAscii(xl_formulas.cellcol)
										lnNdx     = ATC(lcBaseCol, lcSharedFormula, lnCnt)
										DO WHILE True
											IF !ISALPHA(SUBSTR(lcSharedFormula, lnNdx-1, 1)) .AND. ISDIGIT(SUBSTR(lcSharedFormula, lnNdx+LEN(lcBaseCol), 1))
												lcParsedFormula = lcParsedFormula + LEFT(lcSharedFormula, lnNdx-1) + "*!*"
												lcSharedFormula = SUBSTR(lcSharedFormula, lnNdx+LEN(lcBaseCol))
												lnCnt = 1
											ELSE
												lnCnt = lnCnt + 1
											ENDIF
											IF EMPTY(lcSharedFormula)
												EXIT
											ENDIF
											lnNdx = ATC(lcBaseCol, lcSharedFormula, lnCnt)
											IF lnNdx = 0
												lcParsedFormula = lcParsedFormula + lcSharedFormula
												EXIT
											ENDIF
										ENDDO
										REPLACE xl_formulas.parsed WITH lcParsedFormula IN xl_formulas
										lcFormula = STRTRAN(lcParsedFormula, "*!*", TRANSFORM(tnCellRow))
									ELSE
										lcFormula = STRTRAN(lcParsedFormula, "*!*", this.ColumnIndexToAscii(tnCellCol))
									ENDIF
		
								CASE xl_formulas.formrel = SHARED_FORMULA_ROW
									IF EMPTY(lcParsedFormula)
										lnCnt     = 1
										lcBaseRow = TRANSFORM(xl_formulas.cellrow)
										lnNdx     = ATC(lcBaseRow, lcSharedFormula, lnCnt)
										DO WHILE True
											IF lnNdx > 1 .AND. !ISDIGIT(SUBSTR(lcSharedFormula, lnNdx-1, 1)) .AND. !ISDIGIT(SUBSTR(lcSharedFormula, lnNdx+LEN(lcBaseRow), 1))
												lcParsedFormula = lcParsedFormula + LEFT(lcSharedFormula, lnNdx-1) + "*!*"
												lcSharedFormula = SUBSTR(lcSharedFormula, lnNdx+LEN(lcBaseRow))
												lnCnt = 1
											ELSE
												lnCnt = lnCnt + 1
											ENDIF
											IF EMPTY(lcSharedFormula)
												EXIT
											ENDIF
											lnNdx = ATC(lcBaseRow, lcSharedFormula, lnCnt)
											IF lnNdx = 0
												lcParsedFormula = lcParsedFormula + lcSharedFormula
												EXIT
											ENDIF
										ENDDO
										REPLACE xl_formulas.parsed WITH lcParsedFormula IN xl_formulas
										lcFormula = STRTRAN(lcParsedFormula, "*!*", TRANSFORM(tnCellRow))
									ELSE
										lcFormula = STRTRAN(lcParsedFormula, "*!*", TRANSFORM(tnCellRow))
									ENDIF
							ENDCASE
						ELSE
							SET STEP ON   && For debugging; will be ignored in run-time environment
						ENDIF
		
					CASE xl_formulas.formulatype = "array"
		
					CASE xl_formulas.formulatype = "dataTable"
				ENDCASE
			ENDIF
		ELSE
			lcFormula = ""
		ENDIF
		RETURN lcFormula
	ENDPROC

	PROCEDURE GetCellIndent		&& Returns the cell indentation
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lnIndent
		IF PCOUNT() != 4
			RETURN -1
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			lnIndent = xl_cellxfs.indent
		ELSE
			lnIndent = 0
		ENDIF
		RETURN lnIndent
	ENDPROC

	PROCEDURE GetCellNumberFormat		&& Returns the format code for the selected cell
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lnNumFmtId
		IF PCOUNT() != 4
			RETURN -1
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			lnNumFmtId = xl_cellxfs.numFmtId
		ELSE
			lnNumFmtId = 0
		ENDIF
		RETURN lnNumFmtId
	ENDPROC

	PROCEDURE GetCellNumberFormatText		&& Returns the selected cell numeric format as text
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lcFormatCode
		IF PCOUNT() != 4
			RETURN ""
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			IF SEEK(BINTOC(tnWB)+BINTOC(xl_cellxfs.numFmtId), "xl_numfmts", "id")
				lcFormatCode = ALLTRIM(xl_numfmts.formatcode)
			ENDIF
		ELSE
			lcFormatCode = ""
		ENDIF
		RETURN lcFormatCode
	ENDPROC

	PROTECTED PROCEDURE GetCellRecord		&& Positions to the xl_cells record; returns true if found, or false if not found
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		RETURN SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnCellRow)+BINTOC(tnCellCol), "xl_cells", "cellindex")
	ENDPROC

	PROCEDURE GetCellStyle		&& Gets the cell style value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		IF PCOUNT() = 4 .AND. this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			RETURN xl_cells.cellxfs
		ELSE
			RETURN -1
		ENDIF
	ENDPROC

	PROCEDURE GetCellTextRotation		&& Gets the cell text rotation value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lnRotation
		IF PCOUNT() != 4
			RETURN 99
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			lnRotation = xl_cellxfs.rotation
			IF lnRotation > 90
				lnRotation = 90 - lnRotation
			ENDIF
		ELSE
			lnRotation = 0
		ENDIF
		RETURN lnRotation
	ENDPROC

	PROCEDURE GetCellValidation		&& Returns the cell validation settings
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loValiation
		loValiation = CREATEOBJECT("Empty")
		ADDPROPERTY(loValiation, "Type", 0)
		ADDPROPERTY(loValiation, "Style", 0)
		ADDPROPERTY(loValiation, "Operator", 0)
		ADDPROPERTY(loValiation, "AllowBlank", False)
		ADDPROPERTY(loValiation, "ShowInputMsg", False)
		ADDPROPERTY(loValiation, "ShowErrMsg", False)
		ADDPROPERTY(loValiation, "ErrMsg", "")
		ADDPROPERTY(loValiation, "ErrTitle", "")
		ADDPROPERTY(loValiation, "Prompt", "")
		ADDPROPERTY(loValiation, "Formula1", "")
		ADDPROPERTY(loValiation, "Formula2", "")
		IF PCOUNT() < 4
			RETURN loValiation
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. SEEK(BINTOC(xl_cells.validndx), "xl_validation", "validndx")
			loValiation.Type         = xl_validation.vtype
			loValiation.Style        = xl_validation.vstyle
			loValiation.Operator     = xl_validation.voperator
			loValiation.AllowBlank   = xl_validation.allowblank
			loValiation.ShowInputMsg = xl_validation.showinpmsg
			loValiation.ShowErrMsg   = xl_validation.showerrmsg
			loValiation.ErrMsg       = xl_validation.errmsg
			loValiation.ErrTitle     = xl_validation.errtitle
			loValiation.Prompt       = xl_validation.vprompt
			loValiation.Formula1     = xl_validation.formula1
			loValiation.Formula2     = xl_validation.formula2
		ENDIF
		RETURN loValiation
	ENDPROC

	PROCEDURE GetCellValue		&& Returns the value of the cell
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lxValue, lnDays, lnDecimals, lnMSec, lnTime, lnSize, lcType, loException
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		lxValue = .NULL.
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			lnDecimals = SET("DECIMALS")
			TRY
				DO CASE
					CASE xl_cells.datatype = DATA_TYPE_TEXT
						lxValue = ALLTRIM(xl_cells.cellvalue)
		
					CASE xl_cells.datatype = DATA_TYPE_DATE
						lnDays  = CAST(xl_cells.cellvalue AS I)
						lxValue = DATE(1900, 1, 1) + lnDays - 2
			
					CASE xl_cells.datatype = DATA_TYPE_TIME
						SET DECIMALS TO 12
						lnMSec  = CAST(xl_cells.cellvalue AS N(20,12))
						lnTime  = lnMSec * 24 * 60 * 60
						lxValue = DTOT(DATE()) + lnTime
						lxValue = TRANSFORM(HOUR(lxValue)) + ":" + TRANSFORM(MINUTE(lxValue)) + ":" + TRANSFORM(SEC(lxValue))
			
					CASE xl_cells.datatype = DATA_TYPE_DATETIME
						SET DECIMALS TO 12
						lnDays  = CAST(xl_cells.cellvalue AS I)
						lxValue = DATE(1900, 1, 1) + lnDays - 2
						lnMSec  = CAST(xl_cells.cellvalue AS N(20,12)) - lnDays
						lnTime  = lnMSec * 24 * 60 * 60
						lxValue = DTOT(lxValue) + lnTime
			
					CASE xl_cells.datatype = DATA_TYPE_CHAR
						IF xl_cells.stringid > -1
							IF this.GetCursorRecord("xl_strings", tnWB, xl_cells.stringid)
								lxValue = RTRIM(xl_strings.stringval)
							ELSE
								lxValue = ""
							ENDIF
						ELSE
							lxValue = ALLTRIM(xl_cells.cellvalue)
						ENDIF
			
					CASE xl_cells.datatype = DATA_TYPE_INT
						lxValue = CAST(xl_cells.cellvalue AS I)
						IF lxValue != VAL(xl_cells.cellvalue)                       && Fix suggestion by Dan Lauer for integer values larger than VFP max
							lxValue = CAST(xl_cells.cellvalue AS N(20,0))
						ENDIF
			
					CASE xl_cells.datatype = DATA_TYPE_FLOAT
						lnSize = ATC(".", ALLTRIM(xl_cells.cellvalue))
						IF lnSize > 0
							lnSize = lnSize + lnDecimals
						ELSE
							lnSize = LEN(ALLTRIM(xl_cells.cellvalue)) + lnDecimals + 1
						ENDIF
						lcType  = "N(" + TRANSFORM(lnSize) + "," + TRANSFORM(lnDecimals) + ")"
						lxValue = CAST(xl_cells.cellvalue AS &lcType)
			
					CASE xl_cells.datatype = DATA_TYPE_CURRENCY
						lxValue = CAST(xl_cells.cellvalue AS Y)
			
					CASE xl_cells.datatype = DATA_TYPE_PERCENT
						lxValue = CAST(xl_cells.cellvalue AS N(14,8))
						lxValue = TRANSFORM(lxValue * 100) + "%"
			
					CASE xl_cells.datatype = DATA_TYPE_GENERAL
						lxValue = ALLTRIM(xl_cells.cellvalue)
			
					CASE xl_cells.datatype = DATA_TYPE_FORMULA
			
					CASE xl_cells.datatype = DATA_TYPE_NONE
						lxValue = ALLTRIM(xl_cells.cellvalue)
					
					OTHERWISE
						lxValue = ALLTRIM(xl_cells.cellvalue)
				ENDCASE
		
			CATCH TO loException
				SET STEP ON
			FINALLY
				SET DECIMALS TO &lnDecimals
			ENDTRY
		ENDIF
		RETURN lxValue
	ENDPROC

	PROCEDURE GetCellWordWrap		&& Returns the cell word wrap setting
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL lxReturn
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
			lxReturn = xl_cellxfs.wraptext
		ELSE
			lxReturn = .NULL.
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetCellXfsId		&& Returns the Cell XfsId value
		LPARAMETERS tnWB, tnNumFmtId, tnFontId, tnFillId, tnBorderId, tcHAlign, tcVAlign, tnIndent, tnWrapText, tnRotation
		LOCAL lnCellXfsId
		IF SEEK(BINTOC(tnWB)+BINTOC(tnNumFmtId)+BINTOC(tnFontId)+BINTOC(tnFillId)+BINTOC(tnBorderId)+BINTOC(tnIndent)+BINTOC(tnWrapText)+BINTOC(tnRotation)+ALLTRIM(tcHAlign)+ALLTRIM(tcVAlign), "xl_cellxfs", "cellxfs")
			lnCellXfsId = xl_cellxfs.id
		ELSE
			lnCellXfsId = this.GetNextId(tnWB, 0, 'xl_cellxfs')
			INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, rotation, celllocked, cellhidden) ;
				VALUES (tnWB, lnCellXfsId, tnNumFmtId, tnFontId, tnFillId, tnBorderId, tcHAlign, tcVAlign, tnIndent, tnWrapText, tnRotation, 1, 0)
		ENDIF
		RETURN lnCellXfsId
	ENDPROC

	PROTECTED PROCEDURE GetCellXfsRecord		&& Positions the record pointer to xl_cellxfs record; returns true if found, or false if not found
		LPARAMETERS tnWB, tnId
		RETURN SEEK(BINTOC(tnWB)+BINTOC(tnId), "xl_cellxfs", "id")
	ENDPROC

	PROTECTED PROCEDURE GetCheckSum		&& Returns the checksum the string
		LPARAMETERS tcString
		LOCAL lcString, lcCheckSum
		lcString   = ALLTRIM(tcString)
		lcCheckSum = SYS(2007, lcString, 0, 1)
		lcCheckSum = lcCheckSum + PADR(lcString, (230-LEN(lcCheckSum)), "_")
		RETURN lcCheckSum
	ENDPROC

	PROTECTED PROCEDURE GetChildNodeCount		&& Returns the number of child nodes
		LPARAMETERS tcNode
		RETURN OCCURS("<", tcNode) - 2
	ENDPROC

	PROTECTED PROCEDURE GetChildNodeElement		&& Returns the selected child node
		LPARAMETERS tcNode, tnChild
		LOCAL lcElement, lcChild
		lcElement = STREXTRACT(tcNode, '<', '>', tnChild+1, 1)
		lcChild   = STREXTRACT(tcNode, '<'+lcElement+'>', '</'+lcElement+'>', 1, 5)
		IF EMPTY(lcChild)
			lcChild = STREXTRACT(tcNode, '<'+lcElement+' ', '</'+lcElement+'>', 1, 5)
		ENDIF
		IF EMPTY(lcChild)
			lcChild = STREXTRACT(tcNode, '<'+lcElement+' ', '/>', 1, 5)
		ENDIF
		RETURN lcChild
	ENDPROC

	PROCEDURE GetColumnHidden		&& Returns the column hidden setting
		LPARAMETERS tnWB, tnSheet, tnColumn
		LOCAL lxReturn
		IF PCOUNT() < 3
			RETURN .NULL.
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
				lxReturn = IIF(xl_columns.hidden = 1, True, False)
			ELSE
				lxReturn = False
			ENDIF
		ELSE
			lxReturn = .NULL.
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetColumnInColRowPair		&& Returns the column (Ascii value) in a ColRow pair; i.e., returns A from A6 reference
		LPARAMETERS tcColRow
		LOCAL lnNdx, lcCol, lcChr
		lcCol = ""
		FOR lnNdx=1 TO LEN(tcColRow)
			lcChr = SUBSTR(tcColRow, lnNdx, 1)
			IF ISDIGIT(lcChr)
				EXIT
			ENDIF
			lcCol = lcCol + lcChr
		ENDFOR
		RETURN lcCol
	ENDPROC

	PROTECTED PROCEDURE GetColumnObject		&& Gets the column object definition
		LPARAMETERS toGridColumn, taTblFields
		LOCAL lcFieldName, lnNdx, loColumn, lcValue, lxValue
		EXTERNAL ARRAY taTblFields
		
		loColumn = CREATEOBJECT("Empty")
		ADDPROPERTY(loColumn, "ControlSource", toGridColumn.ControlSource)
		ADDPROPERTY(loColumn, "Format", toGridColumn.Format)
		ADDPROPERTY(loColumn, "InputMask", toGridColumn.InputMask)
		ADDPROPERTY(loColumn, "FontBold", toGridColumn.FontBold)
		ADDPROPERTY(loColumn, "FontItalic", toGridColumn.FontItalic)
		ADDPROPERTY(loColumn, "FontName", toGridColumn.FontName)
		ADDPROPERTY(loColumn, "FontSize", toGridColumn.FontSize)
		ADDPROPERTY(loColumn, "BackColor", toGridColumn.BackColor)
		ADDPROPERTY(loColumn, "ForeColor", toGridColumn.ForeColor)
		ADDPROPERTY(loColumn, "DynamicProperties", False)                  && Indicates if any dynamic property is being used
		ADDPROPERTY(loColumn, "DynamicAlignment", toGridColumn.DynamicAlignment)
		ADDPROPERTY(loColumn, "DynamicFontBold", toGridColumn.DynamicFontBold)
		ADDPROPERTY(loColumn, "DynamicFontItalic", toGridColumn.DynamicFontItalic)
		ADDPROPERTY(loColumn, "DynamicFontName", toGridColumn.DynamicFontName)
		ADDPROPERTY(loColumn, "DynamicFontSize", toGridColumn.DynamicFontSize)
		ADDPROPERTY(loColumn, "DynamicBackColor", toGridColumn.DynamicBackColor)
		ADDPROPERTY(loColumn, "DynamicForeColor", toGridColumn.DynamicForeColor)
		ADDPROPERTY(loColumn, "HeaderCaption", toGridColumn.Header1.Caption)
		ADDPROPERTY(loColumn, "HeaderFontBold", toGridColumn.Header1.FontBold)
		ADDPROPERTY(loColumn, "HeaderFontItalic", toGridColumn.Header1.FontItalic)
		ADDPROPERTY(loColumn, "HeaderFontName", toGridColumn.Header1.FontName)
		ADDPROPERTY(loColumn, "HeaderFontSize", toGridColumn.Header1.FontSize)
		ADDPROPERTY(loColumn, "Width", this.ConvertPixelsToExcelUnits(toGridColumn.Width) + 0.7109375)
		ADDPROPERTY(loColumn, "SumColumn", IIF("SUM" $ toGridColumn.Tag, True, False))   && Indicates to add a column summation
		ADDPROPERTY(loColumn, "FormatCode", "")                              && Cell formatting in xls code
		ADDPROPERTY(loColumn, "NumFmtsIndex", 0)                             && Cell number format reference for formatting in XLSX
		ADDPROPERTY(loColumn, "FieldPercent", False)                         && Field is a percent value
		ADDPROPERTY(loColumn, "FieldType", "C")
		ADDPROPERTY(loColumn, "FieldWidth", 255)
		ADDPROPERTY(loColumn, "FieldDecPlaces", 0)
		ADDPROPERTY(loColumn, "ValueStyleId", 0)                             && Format style Id for column values format
		ADDPROPERTY(loColumn, "HeaderStyleId", 0)                            && Format style Id for column header format
		
		*-*	Set the column indicator if Dynamic properties are being used
		IF !EMPTY(loColumn.DynamicAlignment)
			loColumn.DynamicProperties = True
		ELSE
			IF !EMPTY(loColumn.DynamicFontBold)
				loColumn.DynamicProperties = True
			ELSE
				IF !EMPTY(loColumn.DynamicFontItalic)
					loColumn.DynamicProperties = True
				ELSE
					IF !EMPTY(loColumn.DynamicFontName)
						loColumn.DynamicProperties = True
					ELSE
						IF !EMPTY(loColumn.DynamicFontSize)
							loColumn.DynamicProperties = True
						ELSE
							IF !EMPTY(loColumn.DynamicBackColor)
								loColumn.DynamicProperties = True
							ELSE
								IF !EMPTY(loColumn.DynamicForeColor)
									loColumn.DynamicProperties = True
								ENDIF
							ENDIF
						ENDIF
					ENDIF
				ENDIF
			ENDIF
		ENDIF
		
		lcFieldName = toGridColumn.ControlSource
		lcFieldName = IIF('.' $ lcFieldName, GETWORDNUM(lcFieldName, 2, '.'), lcFieldName)
		lnNdx = ASCAN(taTblFields, lcFieldName, 1, ALEN(taTblFields, 1), 1, 15)
		IF lnNdx > 0
			loColumn.FieldType      = taTblFields[lnNdx, 2]
			loColumn.FieldWidth     = taTblFields[lnNdx, 3]
			loColumn.FieldDecPlaces = taTblFields[lnNdx, 4]
		ELSE
			TRY
				lxValue = EVALUATE(toGridColumn.ControlSource)
			CATCH
				lxValue = .NULL.
			ENDTRY
			IF ISNULL(lxValue)
				SET STEP ON
			ELSE
				loColumn.FieldType = VARTYPE(lxValue)
				DO CASE
					CASE loColumn.FieldType $ 'CV'
						loColumn.FieldWidth = LEN(lxValue)
		
					CASE loColumn.FieldType $ 'DTY'
						loColumn.FieldWidth = 8
		
					CASE loColumn.FieldType $ 'IM'
						loColumn.FieldWidth = 4
		
					CASE loColumn.FieldType = 'L'
						loColumn.FieldWidth = 1
		
					CASE loColumn.FieldType = 'B'
						lcValue    = TRANSFORM(lxValue)
						loColumn.FieldWidth = 8
						loColumn.FieldDecPlaces = LEN(lcValue) - AT('.', lcValue)
		
					CASE loColumn.FieldType $ 'NF'
						lcValue = TRANSFORM(lxValue)
						loColumn.FieldDecPlaces = LEN(lcValue) - AT('.', lcValue)
						loColumn.FieldWidth     = 19 - loColumn.FieldDecPlaces
					
					OTHERWISE
						SET STEP ON
				ENDCASE
			ENDIF
		ENDIF
		RETURN loColumn
	ENDPROC

	PROCEDURE GetColumnWidth		&& Returns the column width
		LPARAMETERS tnWB, tnSheet, tnColumn
		LOCAL lxReturn
		IF PCOUNT() < 3
			RETURN .NULL.
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
				lxReturn = xl_columns.width
			ELSE
				lxReturn = -1
			ENDIF
		ELSE
			lxReturn = .NULL.
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetColWidthKeyExpr		&& Gets the key expression for c_colwidths
		LPARAMETERS tnWB, tnSheet, tnColumn
		RETURN BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnColumn)
	ENDPROC

	PROTECTED PROCEDURE GetCursorRecord		&& Gets the specified xl_cursor record; returns true if found, false if not found
		LPARAMETERS tcCursor, tnWB, tnId
		RETURN SEEK(BINTOC(tnWB)+BINTOC(tnId), tcCursor, "id")
	ENDPROC

	PROCEDURE GetCustomNumericFormat		&& Returns the format code for the specified custom numeric format
		LPARAMETERS tnWB, tnNumFormat
		LOCAL lxReturn
		IF SEEK(BINTOC(tnWB)+BINTOC(tnNumFormat), "xl_numfmts", "id")
			lxReturn = ALLTRIM(xl_numfmts.formatcode)
		ELSE
			lxReturn = ""
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROCEDURE GetCustomPaperSize		&& Gets the values for the custom paper size
		LPARAMETERS tnWB, tnSheet
		LOCAL loReturn
		IF PCOUNT() = 2 .AND. this.GetSheetRecord(tnWB, tnSheet)
			loReturn = CREATEOBJECT("Empty")
			ADDPROPERTY(loReturn, "PaperWidth",  xl_sheets.paperwidth)
			ADDPROPERTY(loReturn, "PaperHeight", xl_sheets.paperheight)
			ADDPROPERTY(loReturn, "PaperDimen",  xl_sheets.paperdimen)
		ELSE
			loReturn = .NULL.
		ENDIF
		RETURN loReturn
	ENDPROC

	PROCEDURE GetDisplayGridLines		&& Gets the display grid line setting for the sheet
		LPARAMETERS tnWB, tnSheet
		IF PCOUNT() = 2 .AND. this.GetSheetRecord(tnWB, tnSheet)
			RETURN IIF(xl_sheets.gridlines=1, True, False)
		ELSE
			RETURN .NULL.
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE GetDwgPicDefinition		&& Gets the image drawing settings
		LPARAMETERS tcXdrPic, tcDwgFile
		LOCAL loXdrPic, lcNvPr, lcXfrm, lcRelationships, lcRelsFile, lcRelsFileName, loException, lcRootPath
		loXdrPic = CREATEOBJECT("Empty")
		ADDPROPERTY(loXdrPic, "RelId", 0)
		ADDPROPERTY(loXdrPic, "GblId", 0)
		ADDPROPERTY(loXdrPic, "ImgWidth", 0)
		ADDPROPERTY(loXdrPic, "ImgHeight", 0)
		ADDPROPERTY(loXdrPic, "ImageFile", "")
		ADDPROPERTY(loXdrPic, "ImageName", "")
		
		TRY
			lcNvPr = this.GetNodeElement(tcXdrPic, "xdr:cNvPr", 1)
			loXdrPic.GblId = CAST(this.GetNodeAttributeValue(lcNvPr, "id") AS I)
			loXdrPic.ImageName = this.GetNodeAttributeValue(lcNvPr, "name")
			
			lcBlip = this.GetNodeElement(tcXdrPic, "a:blip", 1)
			loXdrPic.RelId = this.GetNodeAttributeValue(lcBlip, "r:embed")
			
			lcXfrm = this.GetNodeElement(tcXdrPic, "a:xfrm", 1)
			lcXfrm = this.GetNodeElement(lcXfrm, "a:ext", 1)
			loXdrPic.ImgWidth  = CAST(this.GetNodeAttributeValue(lcXfrm, "cx") AS I)
			loXdrPic.ImgHeight = CAST(this.GetNodeAttributeValue(lcXfrm, "cy") AS I)
		
			lcRelsFileName     = STUFF(tcDwgFile, RATC("\", tcDwgFile) + 1, 0, "_rels\") + ".rels"
			lcRelsFile         = FILETOSTR(lcRelsFileName)
			lcRelationships    = this.GetNodeElement(lcRelsFile, 'Relationships', 1, False)
			lcRootPath         = JUSTPATH(lcRelsFileName)
			lcRootPath         = LEFT(lcRootPath, RATC("\", lcRootPath)-1)
			loXdrPic.ImageFile = this.GetTargetFromRelationships(loXdrPic.RelId, lcRootPath, lcRelationships)
		
			loXdrPic.RelId = CAST(SUBSTR(loXdrPic.RelId, 4) AS I)
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
		RETURN loXdrPic
	ENDPROC

	PROTECTED PROCEDURE GetFormatKeyExpr		&& Gets the key expression for t_format
		LPARAMETERS tnNbrFmtId, tnNumDec, tnFontId, tnFillId, tnBorderId, tcHAlign, tcVAlign, tlWordWrap
		RETURN BINTOC(tnNbrFmtId)+BINTOC(tnNumDec)+BINTOC(tnFontId)+BINTOC(tnFillId)+BINTOC(tnBorderId)+tcHAlign+tcVAlign+TRANSFORM(tlWordWrap)
	ENDPROC

	PROTECTED PROCEDURE GetGridColumnFormat		&& Gets the grid column format for the cell formatting
		LPARAMETER toColumn, tnNumFmtsId, tnWB
		LOCAL lnNumFlds, lcFormatCode, lcInputMask, lcWholePart, lcDecPart, lnLenWholePart, lnLenDecPart, lcNonNumeric
		LOCAL lcTempCode, lnNdx, lcChr, lcDollarSign
		LOCAL ARRAY laFields[1, 5]
		DO CASE
			CASE toColumn.FieldType = "C"
				toColumn.NumFmtsIndex = 0
		
			CASE toColumn.FieldType = "D"
				DO CASE
					CASE EMPTY(toColumn.Format)
						toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
			
					CASE toColumn.Format = "D"
						DO CASE
							CASE SET('DATE') = 'AMERICAN'
								tnNumFmtsId = tnNumFmtsId + 1
								toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
								toColumn.FormatCode   = 'd\.m\.yy;@'
		
							CASE SET('DATE') = 'ANSI'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
		
							CASE SET('DATE') = 'BRITISH'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
							CASE SET('DATE') = 'FRENCH'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
							CASE SET('DATE') = 'GERMAN'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
							CASE SET('DATE') = 'ITALIAN'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
							CASE SET('DATE') = 'JAPAN'
								tnNumFmtsId = tnNumFmtsId + 1
								toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
								toColumn.FormatCode   = 'yyyy/m/d;@'
		
							CASE SET('DATE') = 'USA'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
		
							CASE SET('DATE') = 'MDY'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
		
							CASE SET('DATE') = 'DMY'
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
							CASE SET('DATE') = 'YMD'
								tnNumFmtsId = tnNumFmtsId + 1
								toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
								toColumn.FormatCode   = 'yyyymmdd'
							OTHERWISE
								toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
						ENDCASE
		
					CASE toColumn.Format = "E"
						toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
					CASE toColumn.Format = "YS"
						toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
		
					CASE toColumn.Format = "YL"
						toColumn.NumFmtsIndex = CELL_FORMAT_DATE_DMMMYY
		
					OTHERWISE
						toColumn.NumFmtsIndex = CELL_FORMAT_DATE_MMDDYY
				ENDCASE
		
			CASE toColumn.FieldType = "L"
				toColumn.NumFmtsIndex = 0
		
			CASE toColumn.FieldType $ "NFIBY" .AND. '%' $ toColumn.InputMask
				IF '.' $ toColumn.InputMask
					toColumn.FieldPercent = True
					lcInputMask = CHRTRAN(toColumn.InputMask, '%', '')
					IF lcInputMask == "."
						toColumn.NumFmtsIndex = CELL_FORMAT_PERCENT_FLOAT
					ELSE
						lcWholePart = GETWORDNUM(lcInputMask, 1, '.')
						lcDecPart   = GETWORDNUM(lcInputMask, 2, '.')
		
						lnLenWholePart = LEN(lcWholePart)
						lnLenDecPart   = LEN(lcDecPart)
						DO CASE                                                               && Change suggested Doug Hennig
							CASE lnLenWholePart = 3 .AND. lnLenDecPart = 2
								toColumn.NumFmtsIndex = CELL_FORMAT_PERCENT_FLOAT
		
							CASE lnLenWholePart = 3 .AND. lnLenDecPart = 0
								toColumn.NumFmtsIndex = CELL_FORMAT_PERCENT_INTEGER
		
							OTHERWISE
								tnNumFmtsId = tnNumFmtsId + 1
								toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
								toColumn.FormatCode   = REPLICATE('#', lnLenWholePart-1) + '0.' + REPLICATE('0', lnLenDecPart) + '%'
						ENDCASE
					ENDIF
				ELSE
					toColumn.FieldPercent = True
					lcInputMask = CHRTRAN(toColumn.InputMask, '%', '')
					IF EMPTY(lcInputMask)
						toColumn.NumFmtsIndex = CELL_FORMAT_PERCENT_INTEGER
					ELSE
						lnLenWholePart = LEN(lcInputMask)
						IF lnLenWholePart = 3
							toColumn.NumFmtsIndex = CELL_FORMAT_PERCENT_INTEGER
						ELSE
							tnNumFmtsId = tnNumFmtsId + 1
							toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
							toColumn.FormatCode   = REPLICATE('#', lnLenWholePart) + '%'
						ENDIF
					ENDIF
				ENDIF
		
			CASE toColumn.FieldType $ "NFIB"
				DO CASE
					CASE EMPTY(toColumn.Format) .OR. toColumn.Format = "R"                   && No formatting specified; check InputMask property
						IF EMPTY(toColumn.InputMask)
							toColumn.FormatCode = '#0' + IIF(toColumn.FieldDecPlaces = 0, '', '.' + REPLICATE('0', toColumn.FieldDecPlaces))   && Change by Doug Hennig
						ELSE
							lcDecPart    = GETWORDNUM(toColumn.InputMask, 2, '.')
							lcNonNumeric = CHRTRAN(toColumn.InputMask, '!0123456789.,', '')
							DO CASE
								CASE !EMPTY(lcNonNumeric)
									lcTempCode = ""
									FOR lnNdx=1 TO LEN(toColumn.InputMask)
										lcChr = SUBSTR(toColumn.InputMask, lnNdx, 1)
										IF lcChr $ lcNonNumeric
											lcTempCode = lcTempCode + '\' + lcChr
										ELSE
											IF lcChr = '!'
												lcTempCode = lcTempCode + '0'
											ELSE
												lcTempCode = lcTempCode + IIF(lcChr = '.', lcChr, '#')
											ENDIF
										ENDIF
									ENDFOR
									toColumn.FormatCode = lcTempCode
		
								CASE EMPTY(lcDecPart)            && Bug fix by Doug Hennig
									IF ',' $ toColumn.InputMask
										toColumn.FormatCode = '#,##0'
									ELSE
										toColumn.FormatCode = '#0'
									ENDIF
		
								OTHERWISE                        && Bug fix by Doug Hennig
									lnLenDecPart = LEN(lcDecPart)
									IF ',' $ toColumn.InputMask
										toColumn.FormatCode = '#,##0' + IIF(lnLenDecPart=0, '', '.' + REPLICATE('0', lnLenDecPart))     && Change by Doug Hennig
									ELSE
										toColumn.FormatCode = '#0' + IIF(lnLenDecPart=0, '', '.' + REPLICATE('0', lnLenDecPart))        && Change by Doug Hennig
									ENDIF
							ENDCASE
						ENDIF
						tnNumFmtsId = tnNumFmtsId + 1
						toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
		
					CASE toColumn.Format = "^"                                               && Scientific notation
						toColumn.NumFmtsIndex = CELL_FORMAT_EXPONENT
		
					CASE toColumn.Format = "L"                                               && Leading zeros
						IF EMPTY(toColumn.InputMask)
							lnLenWholePart = toColumn.FieldWidth - toColumn.FieldDecPlaces - IIF(toColumn.FieldDecPlaces>0, 1, 0)
							lnLenDecPart   = toColumn.FieldDecPlaces
						ELSE
							lnLenWholePart = LEN(GETWORDNUM(toColumn.InputMask, 1, '.'))
							lnLenDecPart   = LEN(GETWORDNUM(toColumn.InputMask, 2, '.'))
						ENDIF
						tnNumFmtsId = tnNumFmtsId + 1
						toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
						toColumn.FormatCode   = REPLICATE('0', lnLenWholePart) + '.' + REPLICATE('0', lnLenDecPart)
		
					CASE toColumn.Format = "$$"                                              && Display floating currency symbol
						IF EMPTY(toColumn.InputMask)
							DO CASE
								CASE this.VFPSettings.Currency = "$"
									toColumn.NumFmtsIndex = CELL_FORMAT_CURRENCY_RED_PAREN
		
								CASE this.VFPSettings.Currency = "£"
									toColumn.NumFmtsIndex = CELL_FORMAT_CURR_POUNDS_RED
		
								CASE this.VFPSettings.Currency = "€"
									toColumn.NumFmtsIndex = CELL_FORMAT_CURR_EURO_RED
							
								OTHERWISE
									toColumn.NumFmtsIndex = CELL_FORMAT_CURRENCY_RED_PAREN
							ENDCASE
						ELSE
							lnLenDecPart = LEN(GETWORDNUM(toColumn.InputMask, 2, '.'))
							tnNumFmtsId  = tnNumFmtsId + 1
							toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
							IF ',' $ toColumn.InputMask                                      && Change by Doug Hennig
								lcFormatCode = '#,##0'
							ELSE
								lcFormatCode = '#0'
							ENDIF
							toColumn.FormatCode = '&quot;' + this.VFPSettings.Currency + '&quot;' + lcFormatCode + IIF(lnLenDecPart=0, '', '.' + REPLICATE('0', lnLenDecPart))
						ENDIF
		
					CASE toColumn.Format = "$"                                               && Display fixed currency symbol
						IF EMPTY(toColumn.InputMask)
							DO CASE
								CASE this.VFPSettings.Currency = "$"
									toColumn.NumFmtsIndex = CELL_FORMAT_ACC_CURR_US_PAREN
		
								CASE this.VFPSettings.Currency = "£"
									toColumn.NumFmtsIndex = CELL_FORMAT_ACC_CURR_POUNDS
		
								CASE this.VFPSettings.Currency = "€"
									toColumn.NumFmtsIndex = CELL_FORMAT_ACC_CURR_EURO
		
								OTHERWISE
									toColumn.NumFmtsIndex = CELL_FORMAT_ACC_CURR_US_PAREN
							ENDCASE
						ELSE
							lnLenDecPart = LEN(GETWORDNUM(toColumn.InputMask, 2, '.'))
							tnNumFmtsId  = tnNumFmtsId + 1
							toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
							IF ',' $ toColumn.InputMask                                      && Change by Doug Hennig
								lcFormatCode = '#,##0'
							ELSE
								lcFormatCode = '#0'
							ENDIF
							toColumn.FormatCode = '_(&quot;' + this.VFPSettings.Currency + '&quot;* ' + lcFormatCode + IIF(lnLenDecPart=0, '', '.' + REPLICATE('0', lnLenDecPart)) + '_)'
						ENDIF
				ENDCASE
		
			CASE toColumn.FieldType = "T"
				toColumn.NumFmtsIndex = CELL_FORMAT_DATETIME_DDMMMYYYY_TTAM
		
			CASE toColumn.FieldType = "Y"
				IF EMPTY(toColumn.InputMask)
					DO CASE
						CASE this.VFPSettings.Currency = "$"
							toColumn.NumFmtsIndex = CELL_FORMAT_CURRENCY_RED_PAREN
		
						CASE this.VFPSettings.Currency = "£"
							toColumn.NumFmtsIndex = CELL_FORMAT_CURR_POUNDS_RED
		
						CASE this.VFPSettings.Currency = "€"
							toColumn.NumFmtsIndex = CELL_FORMAT_CURR_EURO_RED
		
						OTHERWISE
							toColumn.NumFmtsIndex = CELL_FORMAT_CURRENCY_RED_PAREN
					ENDCASE
				ELSE
					lcDollarSign = IIF("$" $ toColumn.Format, "&quot;" + this.VFPSettings.Currency + "&quot;", "")              && Change by Doug Hennig
					lnLenDecPart = LEN(GETWORDNUM(toColumn.InputMask, 2, "."))
					tnNumFmtsId  = tnNumFmtsId + 1
					toColumn.NumFmtsIndex = this.GetNextId(tnWB, 0, "xl_numfmts")
					DO CASE
						CASE lnLenDecPart > 0 .AND. "," $ toColumn.InputMask
							toColumn.FormatCode = lcDollarSign + "#,##0." + REPLICATE("0", lnLenDecPart)
		
						CASE lnLenDecPart > 0 .AND. !("," $ toColumn.InputMask)
							toColumn.FormatCode = lcDollarSign + "#0." + REPLICATE("0", lnLenDecPart)
						
						CASE lnLenDecPart = 0 .AND. "," $ toColumn.InputMask
							toColumn.FormatCode = lcDollarSign + "#,##0"
		
						CASE lnLenDecPart = 0 .AND. !("," $ toColumn.InputMask)
							toColumn.FormatCode = lcDollarSign + "#0"
					ENDCASE
				ENDIF
		ENDCASE
		RETURN tnNumFmtsId
	ENDPROC

	PROTECTED PROCEDURE GetHeaderFooterKeyExpr		&& Gets the key expression for the header footer table record
		LPARAMETERS tnWB, tnSheet, tnPage, tnSection
		RETURN BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnPage)+BINTOC(tnSection)
	ENDPROC

	PROCEDURE GetImageDimensions		&& Gets the image height and width dimensions
		LPARAMETERS tcImageFile
		LOCAL loSrcImage, loDimens
		loSrcImage = _SCREEN.System.Drawing.Image.FromFile(tcImageFile)
		IF VARTYPE(loSrcImage) != "O" .OR. loSrcImage.GetLastStatus() != 0
			RETURN .NULL.
		ENDIF
		loDimens = CREATEOBJECT("Empty")
		ADDPROPERTY(loDimens, "Width", loSrcImage.Width)
		ADDPROPERTY(loDimens, "Height", loSrcImage.Height)
		RETURN loDimens
	ENDPROC

	PROCEDURE GetImageRelationshipId		&& Gets the image's relationship id
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		IF PCOUNT() = 6
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnBegRow-1)+BINTOC(tnBegCol-1)+BINTOC(tnEndRow-1)+BINTOC(tnEndCol-1), "xl_images", "position")
				RETURN xl_images.relid
			ELSE
				RETURN 0
			ENDIF
		ELSE
			RETURN 0
		ENDIF
	ENDPROC

	PROCEDURE GetInLineFontDefinition		&& Returns the inline formatting definition of cell text for each character group
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loSharedString, loInline, lcAlias, lnBegPos, lnLen, loCharacter
		IF PCOUNT() != 4
			RETURN .NULL.
		ENDIF
		loSharedString = this.GetSharedStringId(tnWB, tnSheet, tnCellRow, tnCellCol)
		IF ISNULL(loSharedString)
			RETURN .NULL.
		ENDIF
		lnBegPos = 1
		lcAlias  = ALIAS()
		loInline = CREATEOBJECT("Empty")
		ADDPROPERTY(loInline, "Workbook", tnWB)
		ADDPROPERTY(loInline, "StringId", loSharedString.StringId)
		ADDPROPERTY(loInline, "StringValue", loSharedString.StringValue)
		ADDPROPERTY(loInline, "Count", 0)
		ADDPROPERTY(loInline, "Characters[1]")
		SELECT xl_strformat
		SET ORDER TO TAG sortorder
		SCAN FOR workbook = tnWB .AND. id = loSharedString.StringId .AND. !DELETED()
			lnLen = LEN(RTRIM(xl_strformat.stringxml))
			loCharacter = CREATEOBJECT("Empty")
			ADDPROPERTY(loCharacter, "BegPos", lnBegPos)
			ADDPROPERTY(loCharacter, "Length", lnLen)
			ADDPROPERTY(loCharacter, "FontName", xl_strformat.fname)
			ADDPROPERTY(loCharacter, "FontSize", xl_strformat.fsize)
			ADDPROPERTY(loCharacter, "FontBold", xl_strformat.fbold)
			ADDPROPERTY(loCharacter, "FontItalic", xl_strformat.fitalic)
			ADDPROPERTY(loCharacter, "FontColor", xl_strformat.fcolor)
			ADDPROPERTY(loCharacter, "Underline", xl_strformat.uline)
			ADDPROPERTY(loCharacter, "StrikeThru", xl_strformat.strkthr)
			ADDPROPERTY(loCharacter, "SubScript", IIF(xl_strformat.fvpos=FONT_VERTICAL_SUBSCRIPT, True, False))
			ADDPROPERTY(loCharacter, "SuperScript", IIF(xl_strformat.fvpos=FONT_VERTICAL_SUPERSCRIPT, True, False))
			loInline.Count = loInline.Count + 1
			DIMENSION loInline.Characters[loInline.Count]
			loInline.Characters[loInline.Count] = loCharacter
			lnBegPos = lnBegPos + lnLen
		ENDSCAN
		SET ORDER TO
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		IF loInline.Count = 0
			loInline = .NULL.
		ENDIF
		RETURN loInline
	ENDPROC

	PROCEDURE GetLastColumnInRow		&& Returns the last column index in the row
		LPARAMETERS tnWB, tnSheet, tnCellRow
		LOCAL lnMaxCol
		IF PCOUNT() < 3
			RETURN .NULL.
		ENDIF
		lnMaxCol = 0
		SELECT MAX(tmp.cellcol) AS maxcol FROM xl_cells AS tmp ;
			WHERE workbook = tnWB ;
				AND tmp.sheet = tnSheet ;
				AND tmp.cellrow = tnCellRow ;
				AND tmp.celldeleted = False ;
			INTO CURSOR t_temp
		
		IF _TALLY > 0
			lnMaxCol = t_temp.maxcol
		ENDIF
		IF USED('t_temp')
			USE IN t_temp
		ENDIF
		RETURN lnMaxCol
	ENDPROC

	PROCEDURE GetLastRowNumber		&& Returns the last row number in the sheet
		LPARAMETERS tnWB, tnSheet
		LOCAL lnLastRow
		LOCAL ARRAY laMaxRow[1]
		IF PCOUNT() < 2
			RETURN .NULL.
		ENDIF
		SELECT MAX(cellrow) FROM xl_cells ;
			WHERE workbook = tnWB ;
				AND sheet = tnSheet ;
				AND celldeleted = False ;
			INTO ARRAY laMaxRow
		
		IF VARTYPE(laMaxRow[1]) = "N"
			lnLastRow = laMaxRow[1]
		ELSE
			lnLastRow = 0
		ENDIF
		RETURN lnLastRow
	ENDPROC

	PROCEDURE GetMaxColumnNumber		&& Returns the max column number for a sheet
		LPARAMETERS tnWB, tnSheet
		LOCAL lnMaxCol
		LOCAL ARRAY laMaxCol[1]
		IF PCOUNT() < 2
			RETURN .NULL.
		ENDIF
		SELECT MAX(tmp.maxcol) AS maxcol FROM xl_rows AS tmp ;
			WHERE tmp.workbook = tnWB ;
				AND tmp.sheet = tnSheet ;
			INTO ARRAY laMaxCol
		
		IF VARTYPE(laMaxCol[1]) = "N"
			lnMaxCol = laMaxCol[1]
		ELSE
			lnMaxCol = 0
		ENDIF
		RETURN NVL(lnMaxCol, 0)
	ENDPROC

	PROCEDURE GetNamedRange		&& Gets the named range
		LPARAMETERS tnWB, tcRangeName
		LOCAL lcRangeName, loRange
		loRange = CREATEOBJECT("Empty")
		ADDPROPERTY(loRange, "BegCol", 0)
		ADDPROPERTY(loRange, "BegRow", 0)
		ADDPROPERTY(loRange, "EndCol", 0)
		ADDPROPERTY(loRange, "EndRow", 0)
		ADDPROPERTY(loRange, "SheetId", 0)
		
		lcRangeName = CHRTRAN(ALLTRIM(tcRangeName), " !@#$%^&*()+={}[]|<>,.'?/" + CHR(34), REPLICATE("_", 26))
		IF SEEK(BINTOC(tnWB)+PADL(lcRangeName, 50, " "), "xl_namerange", "wbrname")
			loRange.BegCol  = xl_namerange.begcol
			loRange.BegRow  = xl_namerange.begrow
			loRange.EndCol  = xl_namerange.endcol
			loRange.EndRow  = xl_namerange.endrow
			loRange.SheetId = xl_namerange.sheet
		ENDIF
		RETURN loRange
	ENDPROC

	PROCEDURE GetNamedRanges		&& Returns an object listing of all named ranges
		LPARAMETERS tnWB
		LOCAL lcRangeName, loRanges
		loRanges = CREATEOBJECT("Empty")
		ADDPROPERTY(loRanges, "List[1]")
		ADDPROPERTY(loRanges, "Count", 0)
		lnCnt = 0
		SELECT xl_namerange
		SCAN FOR wb = tnWB
			lnCnt = lnCnt + 1
			loRanges.Count = lnCnt
			DIMENSION loRanges.List[lnCnt]
			loRanges.List[lnCnt] = CREATEOBJECT("Empty")
			ADDPROPERTY(loRanges.List[lnCnt], "Name",    ALLTRIM(xl_namerange.rname))
			ADDPROPERTY(loRanges.List[lnCnt], "Comment", ALLTRIM(xl_namerange.comment))
			ADDPROPERTY(loRanges.List[lnCnt], "Scope",   xl_namerange.scope)
			ADDPROPERTY(loRanges.List[lnCnt], "BegCol",  xl_namerange.begcol)
			ADDPROPERTY(loRanges.List[lnCnt], "BegRow",  xl_namerange.begrow)
			ADDPROPERTY(loRanges.List[lnCnt], "EndCol",  xl_namerange.endcol)
			ADDPROPERTY(loRanges.List[lnCnt], "EndRow",  xl_namerange.endrow)
			ADDPROPERTY(loRanges.List[lnCnt], "SheetId", xl_namerange.sheet)
		ENDSCAN
		RETURN loRanges
	ENDPROC

	PROTECTED PROCEDURE GetNextId		&& Gets the next sequential Id value
		LPARAMETERS tnWB, tnSheet, tcCursor
		LOCAL lnNdx, lnNextId, lcKey
		DO CASE
			CASE tcCursor = "global"
				lnNdx = ASCAN(this.GlobalId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.GlobalId[lnNdx, 2] = this.GlobalId[lnNdx, 2] + 1
				ELSE
					IF this.GlobalId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.GlobalId, 1) + 1
					ENDIF
					DIMENSION this.GlobalId[lnNdx, 2]
					this.GlobalId[lnNdx, 1] = tnWB
					this.GlobalId[lnNdx, 2] = 2
				ENDIF
				lnNextId = this.GlobalId[lnNdx, 2]
		
			CASE tcCursor = "xl_borders"
				lnNdx = ASCAN(this.LastBorderId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastBorderId[lnNdx, 2] = this.LastBorderId[lnNdx, 2] + 1
				ELSE
					IF this.LastBorderId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastBorderId, 1) + 1
					ENDIF
					DIMENSION this.LastBorderId[lnNdx, 2]
					this.LastBorderId[lnNdx, 1] = tnWB
					this.LastBorderId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastBorderId[lnNdx, 2]
		
			CASE tcCursor = "xl_cellxfs"
				lnNdx = ASCAN(this.LastCellXfsId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastCellXfsId[lnNdx, 2] = this.LastCellXfsId[lnNdx, 2] + 1
				ELSE
					IF this.LastCellXfsId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastCellXfsId, 1) + 1
					ENDIF
					DIMENSION this.LastCellXfsId[lnNdx, 2]
					this.LastCellXfsId[lnNdx, 1] = tnWB
					this.LastCellXfsId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastCellXfsId[lnNdx, 2]
		
			CASE tcCursor = "xl_fills"
				lnNdx = ASCAN(this.LastFillId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastFillId[lnNdx, 2] = this.LastFillId[lnNdx, 2] + 1
				ELSE
					IF this.LastFillId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastFillId, 1) + 1
					ENDIF
					DIMENSION this.LastFillId[lnNdx, 2]
					this.LastFillId[lnNdx, 1] = tnWB
					this.LastFillId[lnNdx, 2] = 2
				ENDIF
				lnNextId = this.LastFillId[lnNdx, 2]
		
			CASE tcCursor = "xl_fonts"
				lnNdx = ASCAN(this.LastFontId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastFontId[lnNdx, 2] = this.LastFontId[lnNdx, 2] + 1
				ELSE
					IF this.LastFontId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastFontId, 1) + 1
					ENDIF
					DIMENSION this.LastFontId[lnNdx, 2]
					this.LastFontId[lnNdx, 1] = tnWB
					this.LastFontId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastFontId[lnNdx, 2]
		
			CASE tcCursor = "xl_sheet_rels"
				lcKey = BINTOC(tnWB)+BINTOC(tnSheet)
				lnNdx = ASCAN(this.LastSheetRelsId, lcKey, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastSheetRelsId[lnNdx, 2] = this.LastSheetRelsId[lnNdx, 2] + 1
				ELSE
					IF EMPTY(this.LastSheetRelsId[1, 1])
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastSheetRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastSheetRelsId[lnNdx, 2]
					this.LastSheetRelsId[lnNdx, 1] = lcKey
					this.LastSheetRelsId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastSheetRelsId[lnNdx, 2]
		
			CASE tcCursor = "xl_drawing_rels"
				lcKey = BINTOC(tnWB)+BINTOC(tnSheet)
				lnNdx = ASCAN(this.LastDrawingRelsId, lcKey, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastDrawingRelsId[lnNdx, 2] = this.LastDrawingRelsId[lnNdx, 2] + 1
				ELSE
					IF EMPTY(this.LastDrawingRelsId[1, 1])
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastDrawingRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastDrawingRelsId[lnNdx, 2]
					this.LastDrawingRelsId[lnNdx, 1] = lcKey
					this.LastDrawingRelsId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastDrawingRelsId[lnNdx, 2]
		
			CASE tcCursor = "xl_ndxcolors"
				lnNdx = ASCAN(this.LastIndexColorId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastIndexColorId[lnNdx, 2] = this.LastIndexColorId[lnNdx, 2] + 1
				ELSE
					IF this.LastIndexColorId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastIndexColorId, 1) + 1
					ENDIF
					DIMENSION this.LastIndexColorId[lnNdx, 2]
					this.LastIndexColorId[lnNdx, 1] = tnWB
					this.LastIndexColorId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastIndexColorId[lnNdx, 2]
		
			CASE tcCursor = "xl_mrucolors"
				lnNdx = ASCAN(this.LastMruColorId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastMruColorId[lnNdx, 2] = this.LastMruColorId[lnNdx, 2] + 1
				ELSE
					IF this.LastMruColorId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastMruColorId, 1) + 1
					ENDIF
					DIMENSION this.LastMruColorId[lnNdx, 2]
					this.LastMruColorId[lnNdx, 1] = tnWB
					this.LastMruColorId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastMruColorId[lnNdx, 2]
		
			CASE tcCursor = "xl_numfmts"
				lnNdx = ASCAN(this.LastNumFmtId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastNumFmtId[lnNdx, 2] = this.LastNumFmtId[lnNdx, 2] + 1
				ELSE
					IF this.LastNumFmtId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastNumFmtId, 1) + 1
					ENDIF
					DIMENSION this.LastNumFmtId[lnNdx, 2]
					this.LastNumFmtId[lnNdx, 1] = tnWB
					this.LastNumFmtId[lnNdx, 2] = START_NUMERIC_FORMAT_ID
				ENDIF
				lnNextId = this.LastNumFmtId[lnNdx, 2]
		
			CASE tcCursor = "xl_workbook_rels"
				lnNdx = ASCAN(this.LastWorkbookRelsId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastWorkbookRelsId[lnNdx, 2] = this.LastWorkbookRelsId[lnNdx, 2] + 1
				ELSE
					IF this.LastWorkbookRelsId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastWorkbookRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastWorkbookRelsId[lnNdx, 2]
					this.LastWorkbookRelsId[lnNdx, 1] = tnWB
					this.LastWorkbookRelsId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastWorkbookRelsId[lnNdx, 2]
		
			CASE tcCursor = "xl_sheets"
				lnNdx = ASCAN(this.LastSheetId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastSheetId[lnNdx, 2] = this.LastSheetId[lnNdx, 2] + 1
				ELSE
					IF this.LastSheetId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastSheetId, 1) + 1
					ENDIF
					DIMENSION this.LastSheetId[lnNdx, 2]
					this.LastSheetId[lnNdx, 1] = tnWB
					this.LastSheetId[lnNdx, 2] = 1
				ENDIF
				lnNextId = this.LastSheetId[lnNdx, 2]
		
			CASE tcCursor = "xl_strings"
				lnNdx = ASCAN(this.LastStringId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastStringId[lnNdx, 2] = this.LastStringId[lnNdx, 2] + 1
				ELSE
					IF this.LastStringId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastStringId, 1) + 1
					ENDIF
					DIMENSION this.LastStringId[lnNdx, 2]
					this.LastStringId[lnNdx, 1] = tnWB
					this.LastStringId[lnNdx, 2] = 0
				ENDIF
				lnNextId = this.LastStringId[lnNdx, 2]
		
			OTHERWISE
				SET STEP ON
				lnNextId = 0
		ENDCASE
		RETURN lnNextId
	ENDPROC

	PROTECTED PROCEDURE GetNodeAttributeValue		&& Returns the attribute value
		LPARAMETERS tcXf, tcAttrName
		RETURN STREXTRACT(tcXf, tcAttrName+'="', '"', 1)
	ENDPROC

	PROTECTED PROCEDURE GetNodeCount		&& Returns the number of nodes to be processed
		LPARAMETERS tcNode, tcElement
		LOCAL lnCount
		lnCount = OCCURS("<"+tcElement+">", tcNode)
		IF lnCount = 0
			lnCount = OCCURS("<"+tcElement+" ", tcNode)
			IF lnCount = 0
				lnCount = OCCURS("<"+tcElement, tcNode)
			ENDIF
		ENDIF
		RETURN lnCount
	ENDPROC

	PROTECTED PROCEDURE GetNodeElement		&& Gets the string content for the node element
		LPARAMETERS tcNode, tcElement, tnNode, tlStrict, tnFlag
		LOCAL lcContent, lnNdx, loException
		DO CASE
			CASE PCOUNT() = 3
				tlStrict = False
				tnFlag   = 5
			
			CASE PCOUNT() = 4
				tnFlag = 5
		ENDCASE
		TRY
			IF tlStrict
				lcContent = STREXTRACT(tcNode, "<"+tcElement+">", "</"+tcElement+">", tnNode, tnFlag)
			ELSE
				lcContent = STREXTRACT(tcNode, "<"+tcElement+" ", ">", tnNode, tnFlag)
				IF RIGHT(lcContent, 2) != "/>"
					lcContent = STREXTRACT(tcNode, "<"+tcElement+" ", "</"+tcElement+">", tnNode, tnFlag)
					IF EMPTY(lcContent)
						lcContent = STREXTRACT(tcNode, "<"+tcElement+">", "</"+tcElement+">", tnNode, tnFlag)
					ENDIF
					IF EMPTY(lcContent)
						lcContent = STREXTRACT(tcNode, "<"+tcElement, "/>", tnNode, tnFlag)
					ENDIF
				ENDIF
			ENDIF
		
		CATCH TO loException
			SET STEP ON
			lcContent = ""
		ENDTRY
		RETURN lcContent
	ENDPROC

	PROTECTED PROCEDURE GetNodeElementValue		&& Gets the value assigned to the node element
		LPARAMETERS tcElement
		RETURN STREXTRACT(tcElement, '>', '<', 1, 1)
	ENDPROC

	PROCEDURE GetNumberOfSheets		&& Returns the number of sheets for the given workbook
		LPARAMETERS tnWB
		LOCAL lnCount
		SELECT xl_sheets
		COUNT FOR xl_sheets.workbook = tnWB .AND. !xl_sheets.shdeleted TO lnCount
		RETURN lnCount
	ENDPROC

	PROCEDURE GetPaperSize		&& Gets the paper size for the selected sheet
		LPARAMETERS tnWB, tnSheet
		LOCAL lxReturn
		IF PCOUNT() = 2 .AND. this.GetSheetRecord(tnWB, tnSheet)
			lxReturn = xl_sheets.papersize
		ELSE
			lxReturn = -1
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROCEDURE GetPrintArea		&& Gets the sheet print area settings
		LPARAMETERS tnWB, tnSheet
		LOCAL loPrintArea
		LOCAL ARRAY laPrintArea[1, 4]
		IF PCOUNT() < 2
			RETURN .NULL.
		ENDIF
		
		SELECT begcol, begrow, endcol, endrow ;
			FROM xl_namerange ;
			WHERE workbook = tnWB ;
				AND sheet = tnSheet ;
				AND rname = PADL("_xlnm.PRINT_AREA", 50, " ") ;
			INTO ARRAY laPrintArea
		
		IF _TALLY > 0
			loPrintArea = CREATEOBJECT("Empty")
			ADDPROPERTY(loPrintArea, "BegCol", laPrintArea[1, 1])
			ADDPROPERTY(loPrintArea, "BegRow", laPrintArea[1, 2])
			ADDPROPERTY(loPrintArea, "EndCol", laPrintArea[1, 3])
			ADDPROPERTY(loPrintArea, "EndRow", laPrintArea[1, 4])
			RETURN loPrintArea
		ELSE
			RETURN .NULL.
		ENDIF
	ENDPROC

	PROCEDURE GetPrintOrientation		&& Gets the print orientation for the sheet output
		LPARAMETERS tnWB, tnSheet
		LOCAL lxReturn
		IF PCOUNT() = 2 .AND. this.GetSheetRecord(tnWB, tnSheet)
			lxReturn = xl_sheets.prnorient
		ELSE
			lxReturn = 0
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetRelationshipFileName		&& Gets the file name for the R:Id value in the relationships XML
		LPARAMETERS tnWB, tnSheet, tnRelId, tcRelsSrc
		LOCAL lcFileName
		lcFileName = ""
		IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRelId)+tcRelsSrc, "xl_relationships", "relidsrc")
			lcFileName = ALLTRIM(xl_relationships.target)
		ENDIF
		RETURN lcFileName
	ENDPROC

	PROTECTED PROCEDURE GetRelationTypeText		&& Gets the relation type text from the code
		LPARAMETERS tcRelsCode
		LOCAL lcRelsText
		DO CASE
			CASE tcRelsCode = RELS_TYPE_CALCCHAIN
				lcRelsText = "calcChain"
		
			CASE tcRelsCode = RELS_TYPE_DRAWING
				lcRelsText = "drawing"
		
			CASE tcRelsCode = RELS_TYPE_EXTNLINK
				lcRelsText = "externalLink"
		
			CASE tcRelsCode = RELS_TYPE_HYPERLINK
				lcRelsText = "hyperlink"
		
			CASE tcRelsCode = RELS_TYPE_IMAGE
				lcRelsText = "image"
		
			CASE tcRelsCode = RELS_TYPE_SHAREDSTRINGS
				lcRelsText = "sharedStrings"
		
			CASE tcRelsCode = RELS_TYPE_STYLES
				lcRelsText = "styles"
		
			CASE tcRelsCode = RELS_TYPE_THEME
				lcRelsText = "theme"
		
			CASE tcRelsCode = RELS_TYPE_WORKSHEET
				lcRelsText = "worksheet"
			
			OTHERWISE
				lcRelsText = ""
		ENDCASE
		RETURN lcRelsText
	ENDPROC

	PROCEDURE GetRGBValues		&& Gets the specified RGB color value
		LPARAMETERS tnColorValue, tcRGB
		LOCAL lnValue, lnNdx
		IF PCOUNT() < 2
			RETURN -1
		ENDIF
		DO CASE
			CASE tcRGB = "R"
				lnValue = tnColorValue
				FOR lnNdx=8 TO 31
					lnValue = BITCLEAR(lnValue, lnNdx)
				ENDFOR
		
			CASE tcRGB = "G"
				lnValue = BITRSHIFT(tnColorValue, 8)
				FOR lnNdx=8 TO 31
					lnValue = BITCLEAR(lnValue, lnNdx)
				ENDFOR
		
			CASE tcRGB = "B"
				lnValue = BITRSHIFT(tnColorValue, 16)
				FOR lnNdx=8 TO 31
					lnValue = BITCLEAR(lnValue, lnNdx)
				ENDFOR
		
			CASE tcRGB = "S"
				RETURN TRANSFORM(INT(GetRGBValues(tnColorValue, 'R'))) + "," + TRANSFORM(INT(GetRGBValues(tnColorValue, 'G'))) + "," + TRANSFORM(INT(GetRGBValues(tnColorValue, 'B')))
		
			OTHERWISE
				RETURN -1
		ENDCASE
		RETURN INT(lnValue)
	ENDPROC

	PROTECTED PROCEDURE GetRowHeightKeyExpr		&& Gets the key expression for the c_rowheights
		LPARAMETERS tnWB, tnSheet, tnRow
		RETURN BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRow)
	ENDPROC

	PROCEDURE GetRowHidden		&& Gets the row hidden setting
		LPARAMETERS tnWB, tnSheet, tnRow
		LOCAL lxReturn
		IF PCOUNT() < 3
			RETURN .NULL.
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRow), "xl_rows", "row")
				lxReturn = IIF(xl_rows.hidden = 1, True, False)
			ELSE
				lxReturn = False
			ENDIF
		ELSE
			lxReturn = .NULL.
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetRowInColRowPair		&& REturns the row (integer value) in a ColRow pair; i.e., return 6 from A6 reference
		LPARAMETERS tcColRow
		LOCAL lcRow, lnNdx, lcChr
		lcRow = ""
		FOR lnNdx=1 TO LEN(tcColRow)
			lcChr = SUBSTR(tcColRow, lnNdx, 1)
			IF ISDIGIT(lcChr)
				lcRow = lcRow + lcChr
			ENDIF
		ENDFOR
		RETURN CAST(lcRow AS INTEGER)
	ENDPROC

	PROCEDURE GetRowMaxColumn		&& Returns the selected row maximum column
		LPARAMETERS tnWB, tnSheet, tnCellRow
		SELECT maxcol FROM xl_rows WHERE workbook = tnWB AND sheet = tnSheet AND row = tnCellRow INTO ARRAY laMaxCol
		RETURN NVL(laMaxCol[1], 0)
	ENDPROC

	PROTECTED PROCEDURE GetRowMaxFontSize		&& Returns the max fontsize used in the row
		LPARAMETERS tnWB, tnSheet, tnRow
		LOCAL lnFontSize
		LOCAL ARRAY laMaxSize[1]
		SELECT MAX(c.fsize) FROM xl_cells AS a ;
			INNER JOIN xl_cellxfs AS b ON b.id = a.cellxfs ;
			INNER JOIN xl_fonts AS c ON c.id = b.fontId ;
			WHERE a.workbook = tnWB AND a.sheet = tnSheet AND a.cellrow = tnRow ;
			INTO ARRAY laMaxSize
		
		IF _TALLY > 0 .AND. VARTYPE(laMaxSize[1]) = "N" .AND. laMaxSize[1] > 0.0
			lnFontSize = laMaxSize[1]
		ELSE
			lnFontSize = 21.4286
		ENDIF
		RETURN lnFontSize
	ENDPROC

	PROTECTED PROCEDURE GetSharedStringId		&& Gets the Shared Strings Id value for the cell text
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL loSharedString
		loSharedString = .NULL.
		IF PCOUNT() < 4
			RETURN loSharedString
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol) .AND. this.GetCursorRecord("xl_strings", tnWB, xl_cells.stringid)
			loSharedString = CREATEOBJECT("Empty")
			ADDPROPERTY(loSharedString, "StringId", xl_cells.stringid)
			ADDPROPERTY(loSharedString, "StringValue", xl_strings.stringval)
		ENDIF
		RETURN loSharedString
	ENDPROC

	PROCEDURE GetSheetIndex		&& Gets the sheet internal index from the sheet name
		LPARAMETERS tnWB, tcShName
		LOCAL lnSheet
		LOCAL ARRAY laRecord[1]
		SELECT sheet FROM xl_sheets WHERE workbook = tnWB AND PADR(ALLTRIM(shname), 30, "#") = PADR(ALLTRIM(tcShName), 30, "#") INTO ARRAY laRecord
		IF VARTYPE(laRecord[1]) = "N"
			lnSheet = laRecord[1]
		ELSE
			lnSheet = 0
		ENDIF
		RETURN lnSheet
	ENDPROC

	PROCEDURE GetSheetName		&& Gets the sheet name
		LPARAMETERS tnWB, tnSheet
		LOCAL ARRAY laRecord[1]
		IF PCOUNT() = 2
			SELECT shname FROM xl_sheets WHERE BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) INTO ARRAY laRecord
			IF VARTYPE(laRecord[1]) = "C"
				lcSheetName = ALLTRIM(laRecord[1])
			ELSE
				lcSheetName = ""
			ENDIF
		ELSE
			lcSheetName = ""
		ENDIF
		RETURN lcSheetName
	ENDPROC

	PROCEDURE GetSheetProtection		&& Returns the sheet protection settings
		LPARAMETERS tnWB, tnSheet
		LOCAL loProtection
		loProtection = CREATEOBJECT("Empty")
		ADDPROPERTY(loProtection, "Locked", False)
		ADDPROPERTY(loProtection, "AlgorithmName", "")
		ADDPROPERTY(loProtection, "Password", "")
		ADDPROPERTY(loProtection, "AutoFilter", True)
		ADDPROPERTY(loProtection, "DeleteColumns", True)
		ADDPROPERTY(loProtection, "DeleteRows", True)
		ADDPROPERTY(loProtection, "FormatCells", True)
		ADDPROPERTY(loProtection, "FormatColumns", True)
		ADDPROPERTY(loProtection, "FormatRows", True)
		ADDPROPERTY(loProtection, "InsertColumns", True)
		ADDPROPERTY(loProtection, "InsertRows", True)
		ADDPROPERTY(loProtection, "InsertHyperlinks", True)
		ADDPROPERTY(loProtection, "PivotTables", True)
		ADDPROPERTY(loProtection, "SelectLockedCells", False)
		ADDPROPERTY(loProtection, "SelectUnlockedCells", False)
		ADDPROPERTY(loProtection, "Sort", True)
		ADDPROPERTY(loProtection, "Objects", True)
		ADDPROPERTY(loProtection, "Scenarios", True)
		
		IF this.GetSheetRecord(tnWB, tnSheet)
			loProtection.Locked              = xl_sheets.protection
			loProtection.AlgorithmName       = xl_sheets.algorithm
			loProtection.Password            = xl_sheets.password
			loProtection.AutoFilter          = xl_sheets.autoFilter
			loProtection.DeleteColumns       = xl_sheets.deletecol
			loProtection.DeleteRows          = xl_sheets.deleterow
			loProtection.FormatCells         = xl_sheets.formatcell
			loProtection.FormatColumns       = xl_sheets.formatcol
			loProtection.FormatRows          = xl_sheets.formatrow
			loProtection.InsertColumns       = xl_sheets.insertcol
			loProtection.InsertRows          = xl_sheets.insertrow
			loProtection.InsertHyperlinks    = xl_sheets.inserthyper
			loProtection.PivotTables         = xl_sheets.Pivottbl
			loProtection.SelectLockedCells   = xl_sheets.Sellocked
			loProtection.SelectUnlockedCells = xl_sheets.Selunlocked
			loProtection.Sort                = xl_sheets.sort
			loProtection.Objects             = xl_sheets.objects
			loProtection.Scenarios           = xl_sheets.scenarios
		ENDIF
		RETURN loProtection
	ENDPROC

	PROTECTED PROCEDURE GetSheetRecord		&& Positions the record in xl_sheets; returns true if record found, or false if not found
		LPARAMETERS tnWB, tnSheet
		RETURN SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_sheets", "sheet")
	ENDPROC

	PROCEDURE GetSheetRowValues		&& Returns the cell values for the given row
		LPARAMETERS tnWB, tnSheet, tnCellRow
		LOCAL lnMaxCol, loRow
		IF PCOUNT() < 3
			RETURN .NULL.
		ENDIF
		lnMaxCol = this.GetMaxColumnNumber(tnWB, tnSheet)
		IF !ISNULL(lnMaxCol) .AND. lnMaxCol > 0
			loRow = CREATEOBJECT("Empty")
			ADDPROPERTY(loRow, "Values[lnMaxCol, 2]", "")
			ADDPROPERTY(loRow, "Count", lnMaxCol)
			loRow.Values = .NULL.
			SELECT tmp.cellcol FROM xl_cells AS tmp ;
				WHERE workbook = tnWB ;
					AND tmp.sheet = tnSheet ;
					AND tmp.cellrow = tnCellRow ;
					AND tmp.celldeleted = False ;
				INTO CURSOR t_temp
		
			SELECT t_temp
			SCAN
				loRow.Values[t_temp.cellcol, 1] = this.GetCellValue(tnWB, tnSheet, tnCellRow, t_temp.cellcol)
				loRow.Values[t_temp.cellcol, 2] = this.GetCellDataType(tnWB, tnSheet, tnCellRow, t_temp.cellcol)
			ENDSCAN
			USE IN t_temp
		ELSE
			loRow = .NULL.
		ENDIF
		RETURN loRow
	ENDPROC

	PROCEDURE GetSheetScale		&& Gets the sheet printing scale
		LPARAMETERS tnWB, tnSheet
		LOCAL lxReturn
		IF PCOUNT() = 2 .AND. this.GetSheetRecord(tnWB, tnSheet)
			lxReturn = xl_sheets.scale
		ELSE
			lxReturn = -1
		ENDIF
		RETURN lxReturn
	ENDPROC

	PROTECTED PROCEDURE GetStringRecord		&& Positions the record pointer to the selected string record
		LPARAMETERS tnWB, tcString
		RETURN SEEK(BINTOC(tnWB)+this.GetCheckSum(tcString), "xl_strings", "stringndx")
	ENDPROC

	PROTECTED PROCEDURE GetStringXML		&& Returns a converted string from XML to text
		LPARAMETERS tcString
		LOCAL lcString, lcOldChr, lcChr, lnChr, lnNdx
		IF LEFT(tcString, 1) != " " .AND. EMPTY(tcString)
			RETURN ""
		ENDIF
		TRY
			IF ISNULL(this.oXDOM)
				lcString = STRTRAN(tcString, '&amp;', CHR(38))
				lcString = STRTRAN(lcString, '&gt;', '>')
				lcString = STRTRAN(lcString, '&lt;', '<')
				lcString = STRTRAN(lcString, '&quot;', '"')
				lnNdx = 1
				DO WHILE ATC("&#", lcString, lnNdx) > 0
					lcOldChr = STREXTRACT(lcString, "&#", ";", lnNdx)
					lnChr    = INT(VAL(lcOldChr))
					IF lnChr < 256
						lcChr    = CHR(lnChr)
						lcOldChr = "&#" + lcOldChr + ";"
						lcString = STRTRAN(lcString, lcOldChr, lcChr)
					ELSE
						lnNdx = lnNdx + 1            && Increment to skip this embedded value
					ENDIF
				ENDDO
			ELSE
				this.oXDOM.loadXML("<value>" + tcString + "</value>")
				lcString = this.oXDOM.Text
			ENDIF
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
		RETURN lcString
	ENDPROC

	PROCEDURE GetStyleFormatId		&& Gets the format style Id for the given numeric format, font format, and fill format
		LPARAMETERS tnWB, tnNumFmtId, tnFillColor, tcFontName, tnFontSize, tlFontBold, tlFontItalic, tnFontColor, tcFontULine, tlFontStrikThr, tcFontVPos
		LOCAL lnFontId, lnFillId
		IF PCOUNT() < 3
			RETURN -1
		ENDIF
		
		*-*	Validate the font parameters
		IF VARTYPE(tcFontName) != 'C' .OR. EMPTY(tcFontName)
			tcFontName = 'Calibri'
		ENDIF
		tcFontName = PROPER(ALLTRIM(tcFontName))
		IF VARTYPE(tnFontSize) != 'N' .OR. EMPTY(tnFontSize) .OR. tnFontSize < 0.5
			tnFontSize = 11
		ELSE
			tnFontSize = INT(tnFontSize)
		ENDIF
		IF VARTYPE(tlFontBold) != 'L'
			tlFontBold = False
		ENDIF
		IF VARTYPE(tlFontItalic) != 'L'
			tlFontItalic = False
		ENDIF
		IF VARTYPE(tnFontColor) = 'N' .AND. BETWEEN(tnFontColor, 0, RGB(255, 255, 255))
			tnFontColor = FLOOR(tnFontColor)
		ELSE
			tnFontColor = RGB(0, 0, 0)
		ENDIF
		IF VARTYPE(tlFontStrikThr) != 'L'
			tlFontStrikThr = False
		ENDIF
		IF VARTYPE(tcFontULine) != 'C' .OR. !INLIST(tcFontULine, UNDERLINE_SINGLE, UNDERLINE_DOUBLE, UNDERLINE_SINGLEACCOUNTING, UNDERLINE_DOUBLEACCOUNTING, UNDERLINE_NONE)
			tcFontULine = UNDERLINE_NONE
		ENDIF
		IF VARTYPE(tcFontVPos) != 'C' .OR. !INLIST(tcFontVPos, FONT_VERTICAL_BASELINE, FONT_VERTICAL_SUBSCRIPT, FONT_VERTICAL_SUPERSCRIPT)
			tcFontVPos = FONT_VERTICAL_BASELINE
		ENDIF
		
		*-*	Get the font record id
		IF SEEK(BINTOC(tnWB)+PADR(tcFontName, 100) + STR(tnFontSize, 5, 1) + TRANSFORM(tlFontBold) + TRANSFORM(tlFontItalic) + PADL(tnFontColor, 15) + PADR(tcFontULine, 16) + ;
				TRANSFORM(tlFontStrikThr) + PADR(tcFontVPos, 11), "xl_fonts", "cellformat")
			lnFontId = xl_fonts.id
		ELSE
			lnFontId = this.GetNextId(tnWB, 0, 'xl_fonts')
			INSERT INTO xl_fonts (workbook, id, fname, fsize, fbold, fitalic, fcolor, uline, strkthr, fvpos) ;
				VALUES (tnWB, lnFontId, tcFontName, tnFontSize, tlFontBold, tlFontItalic, tnFontColor, tcFontULine, tlFontStrikThr, tcFontVPos)
		ENDIF
		
		*-*	Validate the fill parameter
		IF VARTYPE(tnFillColor) = 'N' .AND. BETWEEN(tnFillColor, 0, RGB(255, 255, 255))
			tnFillColor = FLOOR(tnFillColor)
		ELSE
			tnFillColor = RGB(255, 255, 255)
		ENDIF
		
		*-*	Get the fill record id
		IF tnFillColor = RGB(255, 255, 255)
			lnFillId = 0
		ELSE
			IF SEEK(BINTOC(tnWB)+BINTOC(tnFillColor)+BINTOC(tnFillColor)+FILL_STYLE_SOLID, "xl_fills", "fillcolor")
				lnFillId = xl_fills.id
			ELSE
				lnFillId = this.GetNextId(tnWB, 0, 'xl_fills')
				INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, lnFillId, tnFillColor, tnFillColor, FILL_STYLE_SOLID, 0, 0, 0, 0, -1, -1)
			ENDIF
		ENDIF
		
		*-*	Get the style id
		IF SEEK(BINTOC(tnWB)+BINTOC(tnNumFmtId)+BINTOC(lnFontId)+BINTOC(lnFillId), "xl_cellxfs", "gridstyle")
			lnId = xl_cellxfs.id
		ELSE
			lnId = this.GetNextId(tnWB, 0, "xl_cellxfs")
			INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId) ;
				VALUES (tnWB, lnId, tnNumFmtId, lnFontId, lnFillId)
		ENDIF
		RETURN lnId
	ENDPROC

	PROTECTED PROCEDURE GetTargetFromRelationships		&& Gets the target (file name) for the relationship id from the passed relationships file
		LPARAMETERS tcRId, tcRootPath, tcRelationships
		LOCAL lnNode, lcRelationship, lcRId, lcTargetFile
		lnNode = 1
		lcRelationship = this.GetNodeElement(tcRelationships, 'Relationship', lnNode)
		DO WHILE !EMPTY(lcRelationship)
			lcRId = this.GetNodeAttributeValue(lcRelationship, "Id")
			IF lcRId == tcRId
				lcTargetFile = this.GetNodeAttributeValue(lcRelationship, "Target")
				lcTargetFile = CHRTRAN(lcTargetFile, "/", "\")
				DO WHILE "..\" = LEFT(lcTargetFile, 3) .AND. !EMPTY(tcRootPath)
					tcRootPath   = LEFT(tcRootPath, RATC("\", tcRootPath)-1)
					lcTargetFile = SUBSTR(lcTargetFile, 4)
				ENDDO
				RETURN IIF(EMPTY(tcRootPath), "", ADDBS(tcRootPath)) + lcTargetFile
			ENDIF
			lnNode = lnNode + 1
			lcRelationship = this.GetNodeElement(tcRelationships, 'Relationship', lnNode)
		ENDDO
		RETURN .NULL.
	ENDPROC

	PROCEDURE GetValidation		&& Returns the validation definition
		LPARAMETERS tnValidNdx
		LOCAL loValiation
		loValiation = CREATEOBJECT("Empty")
		ADDPROPERTY(loValiation, "Type", 0)
		ADDPROPERTY(loValiation, "Style", 0)
		ADDPROPERTY(loValiation, "Operator", 0)
		ADDPROPERTY(loValiation, "AllowBlank", False)
		ADDPROPERTY(loValiation, "ShowInputMsg", False)
		ADDPROPERTY(loValiation, "ShowErrMsg", False)
		ADDPROPERTY(loValiation, "ErrMsg", "")
		ADDPROPERTY(loValiation, "ErrTitle", "")
		ADDPROPERTY(loValiation, "Prompt", "")
		ADDPROPERTY(loValiation, "Formula1", "")
		ADDPROPERTY(loValiation, "Formula2", "")
		IF PCOUNT() < 1
			RETURN loValiation
		ENDIF
		IF SEEK(BINTOC(tnValidNdx), "xl_validation", "validndx")
			loValiation.Type         = xl_validation.vtype
			loValiation.Style        = xl_validation.vstyle
			loValiation.Operator     = xl_validation.voperator
			loValiation.AllowBlank   = xl_validation.allowblank
			loValiation.ShowInputMsg = xl_validation.showinpmsg
			loValiation.ShowErrMsg   = xl_validation.showerrmsg
			loValiation.ErrMsg       = xl_validation.errmsg
			loValiation.ErrTitle     = xl_validation.errtitle
			loValiation.Prompt       = xl_validation.vprompt
			loValiation.Formula1     = IIF(xl_validation.formula, "=", "") + ALLTRIM(xl_validation.formula1)
			loValiation.Formula2     = IIF(EMPTY(xl_validation.formula2), "", IIF(xl_validation.formula, "=", "") + ALLTRIM(xl_validation.formula2))
		ENDIF
		RETURN loValiation
	ENDPROC

	PROCEDURE GetValidationList		&& Returns a list of validations for the workbook/sheet
		LPARAMETERS tnWB, tnSheet
		LOCAL loValiation, lcAlias
		lcAlias = ALIAS()
		loValiation = CREATEOBJECT("Empty")
		ADDPROPERTY(loValiation, "List[1]")
		ADDPROPERTY(loValiation, "Count", 0)
		SELECT xl_validation
		SCAN FOR BINTOC(workbook)+BINTOC(sheet)
			loValiation.Count = loValiation.Count + 1
			DIMENSION loValiation.List[loValiation.Count, 2]
			loValiation.List[loValiation.Count, 1] = xl_validation.vtype
			loValiation.List[loValiation.Count, 2] = xl_validation.validndx
		ENDSCAN
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		RETURN loValiation
	ENDPROC

	PROCEDURE GetWorkbook		&& Gets the workbook Id from the name
		LPARAMETERS tcName
		LOCAL lcWBName, lnWBIndex
		lcWBName = JUSTSTEM(tcName)
		IF SEEK(PADR(UPPER(lcWBName), 200, "_"), "xl_workbooks", "wbname")
			lnWBIndex = xl_workbooks.workbook
		ELSE
			lnWBIndex = 0
		ENDIF
		RETURN lnWBIndex
	ENDPROC

	PROCEDURE GetWorkbookFileName		&& Gets the file name for the workbook index
		LPARAMETERS tnWB
		LOCAL lcFileName
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			lcFileName = ADDBS(ALLTRIM(xl_workbooks.filepath)) + ALLTRIM(xl_workbooks.wbname)
			lcFileName = FORCEEXT(lcFileName, "xlsx")
		ELSE
			lcFileName = ""
		ENDIF
		RETURN lcFileName
	ENDPROC

	PROCEDURE GetWorkbookProtection		&& Gets the selected workbook protection
		LPARAMETERS tnWB
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			RETURN xl_workbooks.protection
		ELSE
			RETURN .NULL.
		ENDIF
	ENDPROC

	PROCEDURE GetWorkbookSheets		&& Returns the workbook sheets
		LPARAMETERS tnWB
		LOCAL loSheets
		loSheets = CREATEOBJECT("Empty")
		ADDPROPERTY(loSheets, "List[1, 2]")
		ADDPROPERTY(loSheets, "Count", 0)
		SELECT xl_sheets
		SET ORDER TO TAG sheet
		SCAN FOR workbook = tnWB .AND. !shdeleted
			loSheets.Count = loSheets.Count + 1
			DIMENSION loSheets.List[loSheets.Count, 2]
			loSheets.List[loSheets.Count, 1] = xl_sheets.sheet
			loSheets.List[loSheets.Count, 2] = ALLTRIM(xl_sheets.shname)
		ENDSCAN
		SET ORDER TO
		RETURN loSheets
	ENDPROC

	PROTECTED PROCEDURE GetXMLFirstSegment		&& Gets the first segment in a XML structure
		LPARAMETERS toString, tcBegDelimiter
		LOCAL lnBeg, lcVal, lnEnd, lnBegLen, lnEndLen, lcAltDelimiter, lcEndDelimiter, llShort, lcBegDelimiter
		toString.XMLDelimiter = ALLTRIM(tcBegDelimiter, 1, "<", ">", " ")
		lcAltDelimiter = '<' + toString.XMLDelimiter + " "
		lcBegDelimiter = '<' + toString.XMLDelimiter + '>'
		lnBegLen = LEN(lcBegDelimiter)
		lnBeg    = 1
		DO WHILE (lnBeg + lnBegLen) <= toString.Length
			lcVal = SYS(2600, toString.BaseAdr-1 + lnBeg, lnBegLen)
			IF lcVal == lcBegDelimiter .OR. lcVal == lcAltDelimiter
				DO CASE
					CASE lcVal == lcBegDelimiter
						lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
						llShort = False
		
					CASE lcVal == lcAltDelimiter
						lcEndDelimiter = "/>"
						llShort = True
				ENDCASE
				lnEndLen = LEN(lcEndDelimiter)
				lnEnd = lnBeg + lnBegLen
				DO WHILE (lnEnd + lnEndLen - 1) <= toString.Length
					lcVal = SYS(2600, toString.BaseAdr-1 + lnEnd, lnEndLen)
					DO CASE
						CASE lcVal = "<" .AND. !(lcVal == lcEndDelimiter)
							DO WHILE lcVal != ">" .AND. lnEnd <= toString.Length
								lnEnd = lnEnd + 1
								lcVal = SYS(2600, toString.BaseAdr-1 + lnEnd, 1)
							ENDDO
		
						CASE llShort .AND. lcVal = ">"
							lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
							lnEndLen = LEN(lcEndDelimiter)
							llShort  = False
		
						CASE lcVal == lcEndDelimiter
							toString.LastAdr = lnEnd + lnEndLen
							RETURN SYS(2600, toString.BaseAdr-1 + lnBeg, lnEnd - lnBeg + lnEndLen)
					ENDCASE
					lnEnd = lnEnd + 1
				ENDDO
				lnBeg = toString.Length
			ENDIF
			lnBeg = lnBeg + 1
		ENDDO
		toString.LastAdr = toString.Length
		RETURN ""
	ENDPROC

	PROTECTED PROCEDURE GetXMLHeaderFooterText		&& Returns a XML formatted string for the header and footer text
		LOCAL lcText
		IF EMPTY(xl_hdrfootertext.fontname)
			lcText = '"-,'
		ELSE
			lcText = '"' + ALLTRIM(xl_hdrfootertext.fontname) + ','
		ENDIF
		DO CASE
			CASE xl_hdrfootertext.fontstyle = HEADERFOOTER_FONT_STYLE_BOLD
				lcText = lcText + 'Bold"'
		
			CASE xl_hdrfootertext.fontstyle = HEADERFOOTER_FONT_STYLE_ITALIC
				lcText = lcText + 'Italic"'
		
			CASE xl_hdrfootertext.fontstyle = HEADERFOOTER_FONT_STYLE_BOLDITALIC
				lcText = lcText + 'Bold Italic"'
		
			OTHERWISE
				lcText = lcText + 'Regular"'
		ENDCASE
		IF xl_hdrfootertext.fontsize > 0
			lcText = lcText + '&amp;' + TRANSFORM(xl_hdrfootertext.fontsize)
		ENDIF
		IF xl_hdrfootertext.fontcolor > 0
			lcText = lcText + '&amp;K' + this.ConvertColorToHex(xl_hdrfootertext.fontcolor)
		ENDIF
		lcText = lcText + ALLTRIM(xl_hdrfootertext.text)
		RETURN lcText
	ENDPROC

	PROTECTED PROCEDURE GetXMLNextSegment		&& Gets the next XML Segment in a XML structure
		LPARAMETERS toString
		LOCAL lnBeg, lcVal, lnEnd, lnBegLen, lnEndLen, lcAltDelimiter, lcEndDelimiter, llShort, lcBegDelimiter
		lcAltDelimiter = '<' + toString.XMLDelimiter + " "
		lcBegDelimiter = '<' + toString.XMLDelimiter + '>'
		lnBegLen = LEN(lcBegDelimiter)
		lnBeg    = toString.LastAdr
		DO WHILE (lnBeg + lnBegLen) <= toString.Length
			lcVal = SYS(2600, toString.BaseAdr-1 + lnBeg, lnBegLen)
			IF lcVal == lcBegDelimiter .OR. lcVal == lcAltDelimiter
				DO CASE
					CASE lcVal == lcBegDelimiter
						lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
						llShort = False
		
					CASE lcVal == lcAltDelimiter
						lcEndDelimiter = "/>"
						llShort = True
				ENDCASE
				lnEndLen = LEN(lcEndDelimiter)
				lnEnd = lnBeg + lnBegLen
				DO WHILE (lnEnd + lnEndLen - 1) <= toString.Length
					lcVal = SYS(2600, toString.BaseAdr-1 + lnEnd, lnEndLen)
					DO CASE
						CASE lcVal = "<" .AND. !(lcVal == lcEndDelimiter)
							DO WHILE lcVal != ">" .AND. lnEnd <= toString.Length
								lnEnd = lnEnd + 1
								lcVal = SYS(2600, toString.BaseAdr-1 + lnEnd, 1)
							ENDDO
		
						CASE llShort .AND. lcVal = ">"
							lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
							lnEndLen = LEN(lcEndDelimiter)
							llShort  = False
		
						CASE lcVal == lcEndDelimiter
							toString.LastAdr = lnEnd + lnEndLen
							RETURN SYS(2600, toString.BaseAdr-1 + lnBeg, lnEnd - lnBeg + lnEndLen)
					ENDCASE
					lnEnd = lnEnd + 1
				ENDDO
				lnBeg = toString.Length
			ENDIF
			lnBeg = lnBeg + 1
		ENDDO
		toString.LastAdr = toString.Length
		RETURN ""
	ENDPROC

	PROTECTED PROCEDURE GetXMLSegment		&& Gets the selected XML Segment
		LPARAMETERS txString, tcBegDelimiter
		LOCAL lnBeg, lcVal, lnEnd, lnBegLen, lnEndLen, lcAltDelimiter, lcEndDelimiter, lcRtnSegment, loString
		LOCAL llReleaseHeap, lcBegDelimiter
		lcRtnSegment   = ""
		tcBegDelimiter = ALLTRIM(tcBegDelimiter, 1, "<", ">", " ")
		lcAltDelimiter = '<' + tcBegDelimiter + " "
		lcBegDelimiter = '<' + tcBegDelimiter + '>'
		lnBegLen = LEN(lcBegDelimiter)
		DO CASE
			CASE VARTYPE(txString) = 'C'
				llReleaseHeap = True
				loString = this.AddStringToHeap(txString)
		
			CASE VARTYPE(txString) = 'O'
				llReleaseHeap = False
				loString = txString
		
			OTHERWISE
				RETURN ""
		ENDCASE
		lnBeg = 1
		DO WHILE (lnBeg + lnBegLen) < loString.Length
			lcVal = SYS(2600, loString.BaseAdr-1 + lnBeg, lnBegLen)
			IF lcVal == lcBegDelimiter .OR. lcVal == lcAltDelimiter
				DO CASE
					CASE lcVal == lcBegDelimiter
						lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
						llShort = False
		
					CASE lcVal == lcAltDelimiter
						lcEndDelimiter = "/>"
						llShort = True
				ENDCASE
				lnEndLen = LEN(lcEndDelimiter)
				lnEnd = lnBeg + lnBegLen
				DO WHILE (lnEnd + lnEndLen - 1) < loString.Length
					lcVal = SYS(2600, loString.BaseAdr-1 + lnEnd, lnEndLen)
					DO CASE
						CASE lcVal = "<" .AND. !(lcVal == lcEndDelimiter)
							DO WHILE lcVal != ">" .AND. lnEnd < loString.Length
								lnEnd = lnEnd + 1
								lcVal = SYS(2600, loString.BaseAdr-1 + lnEnd, 1)
							ENDDO
		
						CASE llShort .AND. lcVal = ">"
							lcEndDelimiter = STUFF(lcBegDelimiter, 2, 0, "/")
							lnEndLen = LEN(lcEndDelimiter)
							llShort  = False
		
						CASE lcVal == lcEndDelimiter
							lcRtnSegment = SYS(2600, loString.BaseAdr-1 + lnBeg, lnEnd - lnBeg + lnEndLen)
							IF llReleaseHeap
								this.ReleaseStringFromHeap(loString)
							ENDIF
							RETURN lcRtnSegment
					ENDCASE
					lnEnd = lnEnd + 1
				ENDDO
				lnBeg = loString.Length
			ENDIF
			lnBeg = lnBeg + 1
		ENDDO
		IF llReleaseHeap
			this.ReleaseStringFromHeap(loString)
		ENDIF
		RETURN ""
	ENDPROC

	PROTECTED PROCEDURE GetXMLSegmentEx		&& Returns the selected XML Segment
		LPARAMETERS toXML, tcVal, tcSegment, tnBeg
		LOCAL loSegment, lcEndDelimiter, llShort, lnEndLen, lnEnd, lcVal, loException
		loSegment = CREATEOBJECT('Empty')
		ADDPROPERTY(loSegment, "Segment", "")
		ADDPROPERTY(loSegment, "End", 0)
		*-*	Check if the segment does not contain any content or attributes; i.e. <segment/>
		IF SUBSTR(tcVal, LEN(tcSegment)-1, 1) = "/"
			loSegment.End = tnBeg + LEN(tcVal)
			RETURN loSegment
		ENDIF
		*-*	Set the segment end delimiter to search for
		IF SUBSTR(tcVal, LEN(tcSegment)-1, 1) = ">"
			lcEndDelimiter = tcSegment
			llShort = False
		ELSE
			lcEndDelimiter = "/>"
			llShort = True
		ENDIF
		TRY
			lnEndLen = LEN(lcEndDelimiter)
			lnEnd = tnBeg + LEN(tcSegment) - 1
			DO WHILE (lnEnd + lnEndLen - 1) < toXML.Length
				lcVal = SYS(2600, toXML.BaseAdr-1 + lnEnd, lnEndLen)
				DO CASE
					CASE lcVal = "<" .AND. !(lcVal == lcEndDelimiter)
						DO WHILE lcVal != ">" .AND. lnEnd < toXML.Length
							lnEnd = lnEnd + 1
							lcVal = SYS(2600, toXML.BaseAdr-1 + lnEnd, 1)
						ENDDO
			
					CASE llShort .AND. lcVal = ">"
						lcEndDelimiter = tcSegment
						lnEndLen = LEN(lcEndDelimiter)
						llShort  = False
			
					CASE lcVal == lcEndDelimiter
						loSegment.Segment = SYS(2600, toXML.BaseAdr-1 + tnBeg, lnEnd - tnBeg + lnEndLen)
						EXIT
				ENDCASE
				lnEnd = lnEnd + 1
			ENDDO
			loSegment.End = lnEnd
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
		RETURN loSegment
	ENDPROC

	PROTECTED PROCEDURE GetXMLString		&& Gets the string as an XML formatted string
		LPARAMETERS tcString
		LOCAL lcString, lcXMLString, lnNdx, lcChar, lcInvalidChars, loNodeText
		*-*	TJF 2016-12-12 Strip out invalid XML characters (ASCII 0 to 31 except 9 (TAB), 10 (LF), and 13 (CR))
		lcInvalidChars = CHR(0) + CHR(1) + CHR(2) + CHR(3) + CHR(4) + CHR(5) + CHR(6) + CHR(7) + CHR(8) + CHR(11) + CHR(12) + ;
			CHR(14) + CHR(15) + CHR(16) + CHR(17) + CHR(18) + CHR(19) + CHR(20) + CHR(21) + CHR(22) + CHR(23) + ;
			CHR(24) + CHR(25) + CHR(26) + CHR(27) + CHR(28) + CHR(29) + CHR(30) + CHR(31)
		
		lcString = CHRTRAN(tcString, lcInvalidChars, "")
		IF EMPTY(lcString)
			RETURN ""
		ENDIF
		IF ISNULL(this.oXDOM)
			lcString = STRTRAN(lcString, CHR(38), '&amp;')
			lcString = STRTRAN(lcString, '>', '&gt;')
			lcString = STRTRAN(lcString, '<', '&lt;')
			lcString = STRTRAN(lcString, '"', '&quot;')
		
			IF this.CodePage = 0
				lcXMLString = ""
				FOR lnNdx=1 TO LEN(lcString)
					lcChar = SUBSTR(lcString, lnNdx, 1)
					lnChar = ASC(lcChar)
					lcXMLString = lcXMLString + IIF(lnChar < 128, lcChar, "&#" + TRANSFORM(lnChar) + ";")
				ENDFOR
			ELSE
				lcXMLString = lcString
			ENDIF
		ELSE
			loNodeText  = this.oXDOM.createTextNode(lcString)
			lcXMLString = loNodeText.xml
			lcXMLString = STRTRAN(lcXMLString, '"', '&quot;')
		ENDIF
		RETURN lcXMLString
	ENDPROC

	PROTECTED PROCEDURE Init
		LPARAMETERS tnCodePage
		LOCAL loException, lnSelect
		IF PCOUNT() > 0 .AND. VARTYPE(tnCodePage) = "N"
			this.CodePage = tnCodePage
		ENDIF
		this.VFPSettings = CREATEOBJECT("Empty")
		ADDPROPERTY(this.VFPSettings, "ValuesSet", False)
		ADDPROPERTY(this.VFPSettings, "Alias", "")
		ADDPROPERTY(this.VFPSettings, "Currency", "")
		ADDPROPERTY(this.VFPSettings, "Exact", "")
		ADDPROPERTY(this.VFPSettings, "Safety", "")
		ADDPROPERTY(this.VFPSettings, "SetDeleted", "")
		ADDPROPERTY(this.VFPSettings, "SetPoint", "")
		ADDPROPERTY(this.VFPSettings, "Separator", "")
		lnSelect = SELECT()
		this.CreateWorkingCursors()
		IF this.DeclareWinAPI
			this.DeclareDLL()
		ENDIF
		TRY
			this.oXDOM = CREATEOBJECT('MSXML2.DOMDocument')
		CATCH TO loException
			this.oXDOM = .NULL.
		ENDTRY
		this.InitalizeIdValues()
		this.OnInit()
		SELECT (lnSelect)
	ENDPROC

	PROTECTED PROCEDURE InitalizeIdValues		&& Initializes the internal Id values
		DIMENSION this.GlobalId[1, 2]
		this.GlobalId[1, 1] = 0                                              && Workbook Id
		this.GlobalId[1, 2] = 1                                              && Last Global Id
		
		DIMENSION this.LastBorderId[1, 2]
		this.LastBorderId[1, 1] = 0                                          && Workbook Id
		this.LastBorderId[1, 2] = 0                                          && Last Border Id
		
		DIMENSION this.LastCellXfsId[1, 2]
		this.LastCellXfsId[1, 1] = 0                                         && Workbook Id
		this.LastCellXfsId[1, 2] = 0                                         && Last CellXfs Id
		
		DIMENSION this.LastFillId[1, 2]
		this.LastFillId[1, 1] = 0                                            && Workbook Id
		this.LastFillId[1, 2] = 0                                            && Last Fill Id
		
		DIMENSION this.LastFontId[1, 2]
		this.LastFontId[1, 1] = 0                                            && Workbook Id
		this.LastFontId[1, 2] = 0                                            && Last Font Id
		
		DIMENSION this.LastSheetRelsId[1, 2]
		this.LastSheetRelsId[1, 1] = ""                                      && BINTOC(tnWB)+BINTOC(tnSheet)
		this.LastSheetRelsId[1, 2] = 0                                       && Last drawing Id
		
		DIMENSION this.LastDrawingRelsId[1, 2]
		this.LastDrawingRelsId[1, 1] = ""                                    && BINTOC(tnWB)+BINTOC(tnSheet)
		this.LastDrawingRelsId[1, 2] = 0                                     && Last Image Id
		
		DIMENSION this.LastIndexColorId[1, 2]
		this.LastIndexColorId[1, 1] = 0                                      && Workbook Id
		this.LastIndexColorId[1, 2] = 0                                      && Last IndexColor Id
		
		DIMENSION this.LastMruColorId[1, 2]
		this.LastMruColorId[1, 1] = 0                                        && Workbook Id
		this.LastMruColorId[1, 2] = 0                                        && Last mruColor Id
		
		DIMENSION this.LastNumFmtId[1, 2]
		this.LastNumFmtId[1, 1] = 0                                          && Workbook Id
		this.LastNumFmtId[1, 2] = START_NUMERIC_FORMAT_ID                    && Last NumFmt Id
		
		DIMENSION this.LastWorkbookRelsId[1, 2]
		this.LastWorkbookRelsId[1, 1] = 0                                    && Workbook Id
		this.LastWorkbookRelsId[1, 2] = 0                                    && Last Workbook Relationship Id
		
		DIMENSION this.LastSheetId[1, 2]
		this.LastSheetId[1, 1] = 0                                           && Workbook Id
		this.LastSheetId[1, 2] = 0                                           && Last Sheet Id
		
		DIMENSION this.LastStringId[1, 2]
		this.LastStringId[1, 1] = 0                                          && Workbook Id
		this.LastStringId[1, 2] = 0                                          && Last String Id
	ENDPROC

	PROCEDURE InsertCell		&& Inserts a new cell into the sheet
		LPARAMETERS tnWB, tnSheet, tnRow, tnCol, tnShift
		LOCAL llReturn, lnRow
		IF PCOUNT() < 5
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			SELECT xl_cells
			SET ORDER TO TAG reverse
			DO CASE
				CASE tnShift = INSERT_LEFT
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellcol >= tnCol .AND. cellrow = tnRow
						REPLACE xl_cells.cellcol WITH xl_cells.cellcol + 1 IN xl_cells
						this.SetRowMaxColumn(tnWB, tnSheet, xl_cells.cellrow, xl_cells.cellcol)
					ENDSCAN
					llReturn = True
		
				CASE tnShift = INSERT_RIGHT
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellcol > tnCol .AND. cellrow = tnRow
						REPLACE xl_cells.cellcol WITH xl_cells.cellcol + 1 IN xl_cells
						this.SetRowMaxColumn(tnWB, tnSheet, xl_cells.cellrow, xl_cells.cellcol)
					ENDSCAN
					llReturn = True
		
				CASE tnShift = INSERT_BEFORE
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellrow >= tnRow .AND. cellcol = tnCol
						REPLACE xl_cells.cellrow WITH xl_cells.cellrow + 1 IN xl_cells
						this.SetRowMaxColumn(tnWB, tnSheet, xl_cells.cellrow, xl_cells.cellcol)
					ENDSCAN
					llReturn = True
		
				CASE tnShift = INSERT_AFTER
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellrow > tnRow .AND. cellcol = tnCol
						REPLACE xl_cells.cellrow WITH xl_cells.cellrow + 1 IN xl_cells
						this.SetRowMaxColumn(tnWB, tnSheet, xl_cells.cellrow, xl_cells.cellcol)
					ENDSCAN
					llReturn = True
		
				OTHERWISE
					llReturn = False
			ENDCASE
			SET ORDER TO
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE InsertColumn		&& Inserts a new column into the sheet
		LPARAMETERS tnWB, tnSheet, tnCol, tnShift
		LOCAL llReturn
		LOCAL ARRAY laCellXfs[1], laColRecord[1]
		IF PCOUNT() < 4
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			DO CASE
				CASE tnShift = INSERT_LEFT
					SELECT xl_cells
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellcol >= tnCol
						REPLACE xl_cells.cellcol WITH xl_cells.cellcol + 1 IN xl_cells
						IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_cells.cellrow), "xl_rows", "row") .AND. xl_cells.cellcol > xl_rows.maxcol
							REPLACE xl_rows.maxcol WITH xl_cells.cellcol IN xl_rows
						ENDIF
					ENDSCAN
		
					SELECT cellrow, cellxfs FROM xl_cells WHERE workbook = tnWB AND sheet = tnSheet AND cellcol = tnCol-1 INTO CURSOR t_cells
					IF _TALLY > 0
						SELECT t_cells
						SCAN
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs) ;
								VALUES (tnWB, tnSheet, t_cells.cellrow, tnCol, DATA_TYPE_NONE, t_cells.cellxfs)
						ENDSCAN
					ENDIF
					USE IN SELECT('t_cells')
		
					REPLACE xl_columns.col WITH xl_columns.col + 1 FOR xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSheet .AND. xl_columns.col >= tnCol IN xl_columns
					
					SELECT width, bestfit, collevel, collapsed FROM xl_columns WHERE workbook = tnWB AND sheet = tnSheet AND col = tnCol-1 INTO ARRAY laColRecord
					IF VARTYPE(laColRecord[1,1]) = "N"
						INSERT INTO xl_columns (workbook, sheet, col, width, bestfit, collevel, collapsed) ;
							VALUES (tnWB, tnSheet, tnCol, laColRecord[1,1], laColRecord[1,2], laColRecord[1,3], laColRecord[1,4])
					ENDIF
		
					llReturn = True
		
				CASE tnShift = INSERT_RIGHT
					SELECT xl_cells
					SCAN FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellcol > tnCol
						REPLACE xl_cells.cellcol WITH xl_cells.cellcol + 1 IN xl_cells
						IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_cells.cellrow), "xl_rows", "row") .AND. xl_cells.cellcol > xl_rows.maxcol
							REPLACE xl_rows.maxcol WITH xl_cells.cellcol IN xl_rows
						ENDIF
					ENDSCAN
		
					SELECT cellrow, cellxfs FROM xl_cells WHERE workbook = tnWB AND sheet = tnSheet AND cellcol = tnCol INTO CURSOR t_cells
					IF _TALLY > 0
						SELECT t_cells
						SCAN
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs) ;
								VALUES (tnWB, tnSheet, t_cells.cellrow, tnCol+1, DATA_TYPE_NONE, t_cells.cellxfs)
						ENDSCAN
					ENDIF
					USE IN SELECT('t_cells')
		
					REPLACE xl_columns.col WITH xl_columns.col + 1 FOR xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSheet .AND. xl_columns.col > tnCol IN xl_columns
		
					SELECT width, bestfit, collevel, collapsed FROM xl_columns WHERE workbook = tnWB AND sheet = tnSheet AND col = tnCol INTO ARRAY laColRecord
					IF VARTYPE(laColRecord[1,1]) = "N"
						INSERT INTO xl_columns (workbook, sheet, col, width, bestfit, collevel, collapsed) ;
							VALUES (tnWB, tnSheet, tnCol+1, laColRecord[1,1], laColRecord[1,2], laColRecord[1,3], laColRecord[1,4])
					ENDIF
		
					llReturn = True
		
				OTHERWISE
					llReturn = False
			ENDCASE
			
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE InsertRow		&& Inserts a new row into the sheet
		LPARAMETERS tnWB, tnSheet, tnRow, tnShift
		LOCAL llReturn, lnRow, lnNumCellRecs, lnTally
		LOCAL ARRAY laCellXfs[1], laRowRecord[1]
		IF PCOUNT() < 4
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			DO CASE
				CASE tnShift = INSERT_BEFORE
					REPLACE xl_cells.cellrow WITH xl_cells.cellrow + 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSheet .AND. xl_cells.cellrow >= tnRow IN xl_cells
		
					SELECT cellcol, cellxfs FROM xl_cells WHERE workbook = tnWB AND sheet = tnSheet AND cellrow = tnRow+1 INTO CURSOR t_cells
					IF _TALLY > 0
						SELECT t_cells
						SCAN
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs) ;
								VALUES (tnWB, tnSheet, tnRow, t_cells.cellcol, DATA_TYPE_NONE, t_cells.cellxfs)
						ENDSCAN
					ENDIF
					USE IN SELECT('t_cells')
		
					REPLACE xl_rows.row WITH xl_rows.row + 1 FOR xl_rows.workbook = tnWB .AND. xl_rows.sheet = tnSheet .AND. xl_rows.row >= tnRow IN xl_rows
		
					SELECT maxcol, height, rowlevel, collapsed FROM xl_rows WHERE workbook = tnWB AND sheet = tnSheet AND row = tnRow+1 INTO ARRAY laRowRecord
					INSERT INTO xl_rows (workbook, sheet, row, maxcol, height, rowlevel, collapsed, hidden) ;
						VALUES (tnWB, tnSheet, tnRow, NVL(laRowRecord[1, 1], 1), NVL(laRowRecord[1, 2], -1), NVL(laRowRecord[1, 3], 0), NVL(laRowRecord[1, 4], False), 0)
		
					llReturn = True
		
				CASE tnShift = INSERT_AFTER
					REPLACE xl_cells.cellrow WITH xl_cells.cellrow + 1 FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSheet .AND. xl_cells.cellrow > tnRow IN xl_cells
		
					SELECT cellcol, cellxfs FROM xl_cells WHERE workbook = tnWB AND sheet = tnSheet AND cellrow = tnRow INTO CURSOR t_cells
					IF _TALLY > 0
						SELECT t_cells
						SCAN
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs) ;
								VALUES (tnWB, tnSheet, tnRow+1, t_cells.cellcol, DATA_TYPE_NONE, t_cells.cellxfs)
						ENDSCAN
					ENDIF
					USE IN SELECT('t_cells')
		
					REPLACE xl_rows.row WITH xl_rows.row + 1 FOR xl_rows.workbook = tnWB .AND. xl_rows.sheet = tnSheet .AND. xl_rows.row > tnRow IN xl_rows
					
					SELECT maxcol, height, rowlevel, collapsed FROM xl_rows WHERE workbook = tnWB AND sheet = tnSheet AND row = tnRow INTO ARRAY laRowRecord
					INSERT INTO xl_rows (workbook, sheet, row, maxcol, height, rowlevel, collapsed, hidden) ;
						VALUES (tnWB, tnSheet, tnRow+1, NVL(laRowRecord[1, 1], 1), NVL(laRowRecord[1, 2], -1), NVL(laRowRecord[1, 3], 0), NVL(laRowRecord[1, 4], False), 0)
		
					llReturn = True
		
				OTHERWISE
					llReturn =  False
			ENDCASE
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROTECTED PROCEDURE IsArray		&& Determines if the parameter passed is an array
		LPARAMETERS taArray
		LOCAL llIsArray, loException
		IF PCOUNT() = 0
			RETURN .F.
		ENDIF
		TRY
			IF ALEN(taArray, 1) > 0
				llIsArray = .T.
			ELSE
				llIsArray = .F.
			ENDIF
		CATCH TO loException
			llIsArray = .F.
		ENDTRY
		RETURN llIsArray
	ENDPROC

	PROCEDURE IsCellFormula		&& Returns True if cell contains a formula
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		LOCAL llFormula
		IF PCOUNT() < 4
			RETURN .NULL.
		ENDIF
		llFormula = False
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			llFormula = IIF(xl_cells.datatype = DATA_TYPE_FORMULA, True, False)
		ENDIF
		RETURN llFormula
	ENDPROC

	PROTECTED PROCEDURE IsCellReferenceInvalid		&& Checks if the cell reference is invalid (outside of limits)
		LPARAMETERS tnCellRow, tnCellCol
		IF tnCellRow <= 0 .OR. tnCellCol <= 0
			RETURN True
		ENDIF
		IF tnCellRow > LIMITS_MAX_ROWS .OR. tnCellCol > LIMITS_MAX_COLUMNS
			RETURN True
		ENDIF
		RETURN False
	ENDPROC

	PROCEDURE IsFormatStyleDefined		&& Determines if the format is defined as a style
		LPARAMETERS tnWB, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos
		DO CASE
			CASE PCOUNT() < 3
				RETURN .NULL.
		
			CASE PCOUNT() = 3
				tlBold     = False
				tlItalic   = False
				tnFColor   = RGB(0, 0, 0)
				tcULine    = UNDERLINE_NONE
				tlStrikThr = False
				tcVPos     = FONT_VERTICAL_BASELINE
		
			CASE PCOUNT() = 4
				tlItalic   = False
				tnFColor   = RGB(0, 0, 0)
				tcULine    = UNDERLINE_NONE
				tlStrikThr = False
				tcVPos     = FONT_VERTICAL_BASELINE
		
			CASE PCOUNT() = 5
				tnFColor   = RGB(0, 0, 0)
				tcULine    = UNDERLINE_NONE
				tlStrikThr = False
				tcVPos     = FONT_VERTICAL_BASELINE
		
			CASE PCOUNT() = 6
				tcULine    = UNDERLINE_NONE
				tlStrikThr = False
				tcVPos     = FONT_VERTICAL_BASELINE
		
			CASE PCOUNT() = 7
				tlStrikThr = False
				tcVPos     = FONT_VERTICAL_BASELINE
		
			CASE PCOUNT() = 8
				tcVPos     = FONT_VERTICAL_BASELINE
		ENDCASE
		
		IF SEEK(BINTOC(tnWB)+PADR(tcFName, 100) + STR(tnFSize, 5, 1) + TRANSFORM(tlBold) + TRANSFORM(tlItalic) + PADL(tnFColor, 15) + PADR(tcULine, 16) + ;
				TRANSFORM(tlStrikThr) + PADR(tcVPos, 11), "xl_fonts", "cellformat")
		
			IF SEEK(BINTOC(tnWB)+BINTOC(xl_fonts.id)+BINTOC(0)+BINTOC(0)+BINTOC(0)+BINTOC(0)+BINTOC(0), "xl_cellxfs", "cellformat")
				RETURN xl_cellxfs.id
			ELSE
				RETURN .NULL.
			ENDIF
		ELSE
			RETURN .NULL.
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE IsValidBorderStyle		&& Validates the border style value
		LPARAMETERS tcStyle
		IF VARTYPE(tcStyle) != 'C' .OR. EMPTY(tcStyle) .OR. !INLIST(tcStyle, BORDER_STYLE_THIN, BORDER_STYLE_HAIR, BORDER_STYLE_DOTTED, BORDER_STYLE_DASHDOTDOT, ;
																			 BORDER_STYLE_DASHDOT, BORDER_STYLE_DASHED, BORDER_STYLE_THIN, BORDER_STYLE_MEDIUMDASHDOTDOT, ;
																			 BORDER_STYLE_SLANTDASHDOT, BORDER_STYLE_MEDIUMDASHDOT, BORDER_STYLE_MEDIUMDASHED, ;
																			 BORDER_STYLE_MEDIUM, BORDER_STYLE_THICK, BORDER_STYLE_DOUBLE)
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROTECTED PROCEDURE LoadDrawingXML		&& Loads the drawing.xml into memory
		LPARAMETERS tcDrawing
		LOCAL loDrawing, loXML, lnBeg, lcVal, lnMaxSegLen
		
		*-*	Create the drawing return object
		loDrawing = CREATEOBJECT('Empty')
		ADDPROPERTY(loDrawing, "absoluteAnchor", CREATEOBJECT('Empty'))
		ADDPROPERTY(loDrawing.absoluteAnchor, "Count", 0)
		ADDPROPERTY(loDrawing.absoluteAnchor, "List[1]")
		
		ADDPROPERTY(loDrawing, "oneCellAnchor", CREATEOBJECT('Empty'))
		ADDPROPERTY(loDrawing.oneCellAnchor, "Count", 0)
		ADDPROPERTY(loDrawing.oneCellAnchor, "List[1]")
		
		ADDPROPERTY(loDrawing, "twoCellAnchor", CREATEOBJECT('Empty'))
		ADDPROPERTY(loDrawing.twoCellAnchor, "Count", 0)
		ADDPROPERTY(loDrawing.twoCellAnchor, "List[1]")
		
		*-*	Create XML in memory
		loXML = this.AddStringToHeap(tcDrawing)
		
		*-*	Process the xml
		lnMaxSegLen = 19    && Maximum length of the segment name to process + 1 character
		lnBeg = 1
		DO WHILE lnBeg < loXML.Length
			lcVal = SYS(2600, loXML.BaseAdr-1 + lnBeg, lnMaxSegLen)
			DO CASE
				CASE LEFT(lcVal, 18) == "xdr:absoluteAnchor"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</xdr:absoluteAnchor>", lnBeg)
					loDrawing.absoluteAnchor.Count = loDrawing.absoluteAnchor.Count + 1
					DIMENSION loDrawing.absoluteAnchor.List[loDrawing.absoluteAnchor.Count]
					loDrawing.absoluteAnchor.List[loDrawing.absoluteAnchor.Count] = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 17) == "xdr:oneCellAnchor"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</xdr:oneCellAnchor>", lnBeg)
					loDrawing.oneCellAnchor.Count = loDrawing.oneCellAnchor.Count + 1
					DIMENSION loDrawing.oneCellAnchor.List[loDrawing.oneCellAnchor.Count]
					loDrawing.oneCellAnchor.List[loDrawing.oneCellAnchor.Count] = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 17) == "xdr:twoCellAnchor"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</xdr:twoCellAnchor>", lnBeg)
					loDrawing.twoCellAnchor.Count = loDrawing.twoCellAnchor.Count + 1
					DIMENSION loDrawing.twoCellAnchor.List[loDrawing.twoCellAnchor.Count]
					loDrawing.twoCellAnchor.List[loDrawing.twoCellAnchor.Count] = loSegment.Segment
					lnBeg = loSegment.End
			ENDCASE
			lnBeg = lnBeg + 1
		ENDDO
		this.ReleaseStringFromHeap(loXML)
		RETURN loDrawing
	ENDPROC

	PROTECTED PROCEDURE LoadSheetXml		&& Loads the sheet.xml into memory variable
		LPARAMETERS tcSheet
		*-*	Create the return sheet object
		LOCAL loSheet, loXML, lnBeg, lcVal, loSegment, lnMaxSegLen, lnLen
		loSheet = CREATEOBJECT('Empty')
		ADDPROPERTY(loSheet, "sheetViews", "")
		ADDPROPERTY(loSheet, "sheetPr", "")
		ADDPROPERTY(loSheet, "pageMargins", "")
		ADDPROPERTY(loSheet, "printOptions", "")
		ADDPROPERTY(loSheet, "pageSetup", "")
		ADDPROPERTY(loSheet, "sheetProtection", "")
		ADDPROPERTY(loSheet, "cols", "")
		ADDPROPERTY(loSheet, "mergeCells", "")
		ADDPROPERTY(loSheet, "sheetData", "")
		ADDPROPERTY(loSheet, "dataValidations", "")
		ADDPROPERTY(loSheet, "hyperlinks", "")
		ADDPROPERTY(loSheet, "legacyDrawing", "")
		ADDPROPERTY(loSheet, "drawing", "")
		
		*-*	Create XML in memory
		loXML = this.AddStringToHeap(tcSheet)
		
		*-*	Process the xml
		lnMaxSegLen = 17    && Maximum length of the segment name to process + 1 character
		lnBeg = 1
		DO WHILE lnBeg < loXML.Length
			lcVal = SYS(2600, loXML.BaseAdr-1 + lnBeg, lnMaxSegLen)
			DO CASE
				CASE LEFT(lcVal, 13) == "<printOptions"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</printOptions>", lnBeg)
					loSheet.printOptions = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal,  8) == "<sheetPr"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</sheetPr>", lnBeg)
					loSheet.sheetPr = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 11) == "<sheetViews"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</sheetViews>", lnBeg)
					loSheet.sheetViews = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 12) == "<pageMargins"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</pageMargins>", lnBeg)
					loSheet.pageMargins = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 10) == "<pageSetup"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</pageSetup>", lnBeg)
					loSheet.pageSetup = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 16) == "<sheetProtection"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</sheetProtection>", lnBeg)
					loSheet.sheetProtection = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal,  5) == "<cols"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</cols>", lnBeg)
					loSheet.cols = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 11) == "<mergeCells"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</mergeCells>", lnBeg)
					loSheet.mergeCells = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 10) == "<sheetData"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</sheetData>", lnBeg)
					loSheet.sheetData = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 16) == "<dataValidations"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</dataValidations>", lnBeg)
					loSheet.dataValidations = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 11) == "<hyperlinks"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</hyperlinks>", lnBeg)
					loSheet.hyperlinks = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 14) == "<legacyDrawing"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</legacyDrawing>", lnBeg)
					loSheet.legacyDrawing = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal,  8) == "<drawing"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</drawing>", lnBeg)
					loSheet.drawing = loSegment.Segment
					lnBeg = loSegment.End
			ENDCASE
			lnBeg = lnBeg + 1
		ENDDO
		this.ReleaseStringFromHeap(loXML)
		RETURN loSheet
	ENDPROC

	PROTECTED PROCEDURE LoadStylesXml		&& Loads the styles.xml into memory
		LPARAMETERS tcStyleSheet
		LOCAL loStyles, loXML, lnBeg, lcVal, lnMaxSegLen
		
		*-*	Create the styles return object
		loStyles = CREATEOBJECT('Empty')
		ADDPROPERTY(loStyles, "cellXfs", "")
		ADDPROPERTY(loStyles, "numFmts", "")
		ADDPROPERTY(loStyles, "fonts", "")
		ADDPROPERTY(loStyles, "fills", "")
		ADDPROPERTY(loStyles, "borders", "")
		ADDPROPERTY(loStyles, "colors", "")
		
		*-*	Create XML in memory
		loXML = this.AddStringToHeap(tcStyleSheet)
		
		*-*	Process the xml
		lnMaxSegLen = 9                                                     && Maximum length of the segment name to process + 1 character
		lnBeg = 1
		DO WHILE lnBeg < loXML.Length
			lcVal = SYS(2600, loXML.BaseAdr-1 + lnBeg, lnMaxSegLen)
			DO CASE
				CASE LEFT(lcVal, 8) == "<cellXfs"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</cellXfs>", lnBeg)
					loStyles.cellXfs = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 8) == "<numFmts"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</numFmts>", lnBeg)
					loStyles.numFmts = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 6) == "<fonts"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</fonts>", lnBeg)
					loStyles.fonts = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 6) == "<fills"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</fills>", lnBeg)
					loStyles.fills = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 8) == "<borders"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</borders>", lnBeg)
					loStyles.borders = loSegment.Segment
					lnBeg = loSegment.End
		
				CASE LEFT(lcVal, 7) == "<colors"
					loSegment = this.GetXMLSegmentEx(loXML, lcVal, "</colors>", lnBeg)
					loStyles.colors = loSegment.Segment
					lnBeg = loSegment.End
			ENDCASE
			lnBeg = lnBeg + 1
		ENDDO
		this.ReleaseStringFromHeap(loXML)
		RETURN loStyles
	ENDPROC

	PROCEDURE MergeCells		&& Merges cells together
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		LOCAL lnRow, lnCol, lcCellValue, lnStringIndex, lcDataType, lcCellFormula, llReturn, lnFormulaSi, lcFormulaType
		IF PCOUNT() < 6
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnBegRow)+BINTOC(tnBegCol), "xl_mergecells", "mergecell")
				REPLACE xl_mergecells.endrow WITH tnEndRow, ;
						xl_mergecells.endcol WITH tnEndCol IN xl_mergecells
			ELSE
		*-*		Check if a cell being merged is already in a merge group
				SELECT xl_mergecells
				LOCATE FOR workbook = tnWB .AND. sheet = tnSheet .AND. BETWEEN(tnBegRow, begrow, endrow) .AND. BETWEEN(tnBegCol, begcol, endcol) .AND. !DELETED()
				IF FOUND()
					llReturn = False
				ELSE
					LOCATE FOR workbook = tnWB .AND. sheet = tnSheet .AND. BETWEEN(tnEndRow, begrow, endrow) .AND. BETWEEN(tnEndCol, begcol, endcol) .AND. !DELETED()
					IF FOUND()
						llReturn = False
					ELSE
		*-*				Add merged cells
						INSERT INTO xl_mergecells (workbook, sheet, begrow, begcol, endrow, endcol) ;
							VALUES (tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol)
		*-*				Get upper left most cell value -> will be value of the merged cells (clear all other cell values)
						SELECT xl_cells
						lcCellValue = .NULL.
						FOR lnRow=tnBegRow TO tnEndRow
							FOR lnCol=tnBegCol TO tnEndCol
								IF this.GetCellRecord(tnWB, tnSheet, lnRow, lnCol) .AND. !EMPTY(xl_cells.cellvalue)
									IF ISNULL(lcCellValue) .AND. !xl_cells.celldeleted
										lcDataType    = xl_cells.datatype
										lcCellValue   = xl_cells.cellvalue
										lcFormulaType = xl_cells.formulatype
										lnFormulaSi   = xl_cells.formulasi
										lnStringIndex = xl_cells.stringid
									ENDIF
									REPLACE xl_cells.cellvalue WITH "", ;
											xl_cells.stringid  WITH 0, ;
											xl_cells.datatype  WITH "" IN xl_cells
								ENDIF
							ENDFOR
						ENDFOR
						IF !ISNULL(lcCellValue)
							IF this.GetCellRecord(tnWB, tnSheet, tnBegRow, tnBegCol)
								REPLACE xl_cells.cellvalue   WITH lcCellValue, ;
										xl_cells.stringid    WITH lnStringIndex, ;
										xl_cells.datatype    WITH lcDataType, ;
										xl_cells.formulatype WITH lcFormulaType, ;
										xl_cells.formulasi   WITH lnFormulaSi, ;
										xl_cells.celldeleted WITH False IN xl_cells
							ENDIF
						ENDIF
		*-*				If the tnEndCol cell is not a cell already in the row then add the cell to the cursor for the max cells by row determination
		*				SELECT xl_cells
		*				FOR lnRow=tnBegRow TO tnEndRow
		*					LOCATE FOR workbook = tnWB .AND. sheet = tnSheet .AND. cellrow = lnRow .AND. cellcol >= tnEndCol .AND. !DELETED()
		*					IF !FOUND()
		*						INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, numdec, celldeleted) ;
		*							VALUES (tnWB, tnSheet, lnRow, tnEndCol, DATA_TYPE_NONE, 0, -1, False)
		*					ENDIF
		*				ENDFOR
						llReturn = True
					ENDIF
				ENDIF
			ENDIF
			FOR lnRow=tnBegRow TO tnEndRow
				this.SetRowMaxColumn(tnWB, tnSheet, lnRow, tnEndCol)
			ENDFOR
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE OnDestroy		&& Called by the Destroy() event - user implementation
		*-*
		*-*	TODO: Add your code here
		*-*
	ENDPROC

	PROCEDURE OnInit		&& User code for Init() Event
		*-*
		*-*	TODO: Add your code here
		*-*
	ENDPROC

	PROCEDURE OnShowErrorMessage		&& Event for displaying an error message to the user
		LPARAMETERS tnErrorId, tcErrMessage
		*-*
		*-*	TODO: Use BINDEVENTS to bind to this event and display a message to your users
		*-*
	ENDPROC

	PROCEDURE OnShowStatusMessage		&& Event for displaying a wait message to the user
		LPARAMETERS tnMode, tnStage, tnTotStages
		*-*
		*-*	TODO: Use BINDEVENTS to bind to this event and display a message to your users
		*-*
		*-*	tnTotStages = total number of tnStages (only passed on first call)
		*-*
		*-*	tnMode = 1; opening an xlsx file
		*-* tnStage = 0; start of open
		*-* tnStage = 1; reading shared strings XML
		*-* tnStage = 2; reading styles XML
		*-* tnStage = 3; reading relationships XML
		*-* tnStage = 4; reading sheets XML
		*-* tnStage = 5; reading named ranges
		*-* tnStage = 6; reading external references
		*-* tnStage = -1; end of open
		*-*
		*-*	tnMode = 2; saving an xlsx file
		*-* tnStage = 0; start of save
		*-* tnStage = 1; indicates saving supporting XMLs
		*-* tnStage = 2; indicates saving strings XML
		*-* tnStage = 3; indicates saving styles XML
		*-* tnStage = 4; indicates saving workbook
		*-* tnStage = 5; indicates saving relationship XML
		*-* tnStage = 6+; indicates saving sheets
		*-* tnStage = -1; end of close
		*-*
		*-*	tnMode = 3; saving an xlsx file
		*-* tnStage = 0; start of write of data to cell values
		*-* tnStage = 1-n; indicates saving cell values
		*-* tnStage = -1; write of data
		*-*
	ENDPROC

	PROCEDURE OpenCreatedXlsxFile		&& Opens the XLSX file via Win32 API default program
		LPARAMETERS txWB
		LOCAL lcFileName
		DO CASE
			CASE PCOUNT() = 0
				RETURN False
			
			CASE VARTYPE(txWB) = "N"
				lcFileName = this.GetWorkbookFileName(txWB)
				IF EMPTY(lcFileName) .OR. !FILE(lcFileName)
					RETURN False
				ENDIF
		
			CASE VARTYPE(txWB) = "C"
				IF FILE(txWB)
					lcFileName = txWB
				ELSE
					RETURN False
				ENDIF
		
			OTHERWISE
				RETURN False
		ENDCASE
		TRY
			apiShellExecute(0, "Open", lcFileName, "", "", 5)
		
		CATCH TO loException
			RAISEEVENT(this, "OnShowErrorMessage", 99, "Failure to open file: " + loException.Message)
		ENDTRY
	ENDPROC

	PROCEDURE OpenXlsxFileAsZip		&& Opens the xlsx file and extracts the xml files to a temporary folder [override this method for an alternate way to extract the files to the folder]
		LPARAMETERS tcFileName, tcTempPath
		LOCAL lcZipName, loShell, lnCnt, lnCntTot, loException, llFileOpen
		TRY
			lcZipName = ADDBS(SYS(2023)) + SYS(2015) + ".zip"
			COPY FILE (tcFileName) TO (lcZipName)
			MKDIR (tcTempPath)
			loShell = CREATEOBJECT("shell.application")
			loShell.NameSpace(tcTempPath).CopyHere(loShell.NameSpace(lcZipName).Items, FOF_SILENT)
			lnCnt = 0
			lnCntTot = loShell.NameSpace(lcZipName).Items.Count
			DO WHILE loShell.NameSpace(tcTempPath).Items.Count != lnCntTot
				lnCnt = lnCnt + 1
				apiSleep(100)
				IF lnCnt > lnCntTot
					SET STEP ON
				ENDIF
			ENDDO
			ERASE (lcZipName)
			llFileOpen = True
		
		CATCH TO loException
			SET STEP ON
			this.ErrorLevelId = 2
			RAISEEVENT(this, "OnShowErrorMessage", 2, loException.Message)
			llFileOpen = False
		ENDTRY
		RETURN llFileOpen
	ENDPROC

	PROCEDURE OpenXlsxWorkbook		&& Opens the passed Xlsx workbook and loads the internal cursors with the content
		LPARAMETERS tcFileName, tlForceTextFormat, tlReadGraphicData
		LOCAL lnWB, lcTempPath, lcRelations, lnNode, lcTgtFile, lnTgtSheet, lcTgtSheet, loRange, lcContType, lcFileRelsCnt, lnShId, llFileOpen
		LOCAL lnRelId, lcRelType, lcTarget, lcWbXmlPath, lcRelation, lcWorkbook, lcSheet, lcShName, lcFileContent, loRelations, lcRelId, loWBSheets
		LOCAL loException, loFS, lcDefinedNames, lcDefinedName, lcRName, lcComment, lnRangeId, lcRange, lnNdx, lnShState, lcFileExt, lcRelationships, lcShState
		LOCAL lcCellRange, loDir, lnLastRelId, lcWBSheets, lcContentTypes, lcContentType, lcTmpSheet, llStringsXMLAdded, llStringsXMLRels
		LOCAL llAdded, lcFileName
		LOCAL ARRAY laTemp[1]
		llStringsXMLAdded = False
		llStringsXMLRels  = False
		DO CASE
			CASE PCOUNT() = 0
				this.ErrorLevelId = 1
				RAISEEVENT(this, "OnShowErrorMessage", 1, "Invalid call to OpenXlsxWorkbook() method; must include file name to open")
				RETURN 0
		
			CASE PCOUNT() = 1
				tlForceTextFormat = False
				tlReadGraphicData = True
		
			CASE PCOUNT() = 2
				tlReadGraphicData = True
		ENDCASE
		lnWB = 0
		IF ADIR(laTemp, tcFileName) > 0
		*-*	Open the Workbook as an archive (zip) file and extract contents
			lcTempPath = ADDBS(SYS(2023)) + SYS(2015)
			llFileOpen = this.OpenXlsxFileAsZip(tcFileName, lcTempPath)
			IF !llFileOpen
				RETURN 0
			ENDIF
		
		*-*	Save system settings
			this.SaveVFPSettings()
		
		*-*	Add to the workbook table
			lnWB = this.CreateWorkbookEx(tcFileName)
			IF lnWB > 0
		*-*		Verify all required XML files are present
				lcWbXmlPath = IIF(RIGHT(lcTempPath, 1) = "\", LEFT(lcTempPath, LEN(lcTempPath)-1), lcTempPath)
				IF ADIR(laTemp, lcWbXmlPath + "\xl\workbook.xml") = 0
					this.ErrorLevelId = 3
					RAISEEVENT(this, "OnShowErrorMessage", 3, "Invalid XLSX file - missing workbook.xml")
					this.DeleteWorkbook(lnWB)
					this.RestoreVFPSettings()
					RETURN 0		
				ENDIF
				IF ADIR(laTemp, lcWbXmlPath + "\xl\_rels\workbook.xml.rels") = 0
					this.ErrorLevelId = 4
					RAISEEVENT(this, "OnShowErrorMessage", 4, "Invalid XLSX file - missing workbook.xml.rels")
					this.DeleteWorkbook(lnWB)
					this.RestoreVFPSettings()
					RETURN 0
				ENDIF
		
		*-*		Process the workbook
				RAISEEVENT(this, "OnShowStatusMessage", 1, 1, 6)
				TRY
		*-*			Get the default content types
					lcContentTypes = FILETOSTR(lcWbXmlPath + "\[Content_Types].xml")
					lnNode = 1
					lcContentType = this.GetNodeElement(lcContentTypes, 'Default', lnNode)
					DO WHILE !EMPTY(lcContentType)
						lcFileExt  = this.GetNodeAttributeValue(lcContentType, "Extension")
						lcContType = this.GetNodeAttributeValue(lcContentType, "ContentType")
						
						IF tlReadGraphicData
							INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (lnWB, lcFileExt, lcContType)
						ELSE
							IF lcContType != "image"
								INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (lnWB, lcFileExt, lcContType)
							ENDIF
						ENDIF
		
		*-*				Check if this is a macro enabled workbook
						IF RIGHT(lcContType, 10) = "vbaProject"
							UPDATE xl_workbooks SET macroenabled = True WHERE workbook = lnWB
							EXIT
						ENDIF
		
						lnNode = lnNode + 1
						lcContentType = this.GetNodeElement(lcContentTypes, 'Default', lnNode)
					ENDDO
		
		*-*			Set base path to workbook files
					lcWbXmlPath = lcWbXmlPath + "\xl\"
		
		*-*			Load the workbook files into memory for processing
					lcWorkbook = FILETOSTR(lcWbXmlPath + "workbook.xml")
					lcWBSheets = this.GetNodeElement(lcWorkbook, 'sheets', 1, False, 1)
					loWBSheets = this.AddStringToHeap(lcWBSheets)
		
		*-*			Get the defined relationship files for this workbook; process each file
					RAISEEVENT(this, "OnShowStatusMessage", 1, 3)
					lnLastRelId = 0
					lcRelations = FILETOSTR(lcWbXmlPath + "_rels\workbook.xml.rels")
					loRelations = this.AddStringToHeap(lcRelations)
					lcRelation  = this.GetXMLFirstSegment(loRelations, 'Relationship')
					DO WHILE !EMPTY(lcRelation)
						lcRelId    = this.GetNodeAttributeValue(lcRelation, "Id")
						lnRelId    = CAST(SUBSTR(lcRelId, 4) AS I)
						lcTarget   = this.GetNodeAttributeValue(lcRelation, "Target")
						lcRelType  = this.GetNodeAttributeValue(lcRelation, "Type")
						lcRelType  = SUBSTR(lcRelType, RATC("/", lcRelType)+1)
						lcFileName = lcWbXmlPath + lcTarget
						IF ADIR(laTemp, lcFileName) > 0
							DO CASE
								CASE lcRelType = "calcChain"
		*-*							Not supported; this is dependent on the order the cells were last calculated by program version
			
								CASE lcRelType = "externalLink"
									lcFileContent = this.ConvertFileToString(lcFileName, 13)
									lcFileRelsCnt = this.ConvertFileToString(lcWbXmlPath + ADDBS(JUSTPATH(lcTarget)) + "_rels\" + JUSTSTEM(lcTarget) + ".xml.rels", 13)
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent, relscontent) ;
										VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_EXTNLINK, lcTarget, lcFileContent, lcFileRelsCnt)
			
								CASE lcRelType = "sharedStrings"
									IF !this.ReadSharedStringsXML(lcFileName, lnWB)
										this.ErrorLevelId = 7
										RAISEEVENT(this, "OnShowErrorMessage", 7, "Invalid XLSX file - error during shared string loading")
										THROW
									ENDIF
									llStringsXMLRels = True
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
										VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_SHAREDSTRINGS, lcTarget)
			
								CASE lcRelType = "styles"
									this.ReadStylesXML(lcFileName, lnWB)
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
										VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_STYLES, lcTarget)
			
								CASE lcRelType = "theme"
									lcFileContent = FILETOSTR(lcFileName)
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent) ;
										VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_THEME, lcTarget, lcFileContent)
			
								CASE lcRelType = "vbaProject"
									lcFileContent = this.ConvertFileToString(lcFileName, 13)
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent) ;
										VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_VBAPROJECT, lcTarget, lcFileContent)
			
								CASE lcRelType = "worksheet"
									lcSheet = ""
									lcTmpSheet = this.GetXMLFirstSegment(loWBSheets, 'sheet')
									DO WHILE !EMPTY(lcTmpSheet)
										IF ATC(lcRelId, lcTmpSheet) > 0
											lcSheet = lcTmpSheet
											EXIT
										ENDIF
										lcTmpSheet = this.GetXMLNextSegment(loWBSheets)
									ENDDO
									IF EMPTY(lcSheet)
										SET STEP ON
										this.ErrorLevelId = 8
										RAISEEVENT(this, "OnShowErrorMessage", 8, "Invalid XLSX file - missing sheet or invalid sheet: " + lcTarget)
										this.DeleteWorkbook(lnWB)
										THROW
									ENDIF
									lnShId    = CAST(CHRTRAN(lcTarget, CHRTRAN(lcTarget, '1234567890', ''), '') AS I)
									lcShName  = this.GetStringXML(this.GetNodeAttributeValue(lcSheet, "name"))
									lcShState = this.GetNodeAttributeValue(lcSheet, "state")
									DO CASE
										CASE lcShState = "visible"
											lnShState = VISIBLE_SHEET_STATE
			
										CASE lcShState = "hidden"
											lnShState = HIDDEN_SHEET_STATE
			
										CASE lcShState = "veryHidden"
											lnShState = VERYHIDDEN_SHEET_STATE
			
										OTHERWISE
											lnShState = VISIBLE_SHEET_STATE
									ENDCASE
		
									llAdded = this.ReadSheetXML(lcFileName, lnWB, lnShId, lcShName, lnShState, tlForceTextFormat, tlReadGraphicData)
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
										VALUES (lnWB, lnShId, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_WORKSHEET, lcTarget)
			
									this.SetLastId(lnWB, 0, lnShId, "xl_sheets")
									IF llAdded .AND. !llStringsXMLAdded
										llStringsXMLAdded = llAdded
									ENDIF
			
								OTHERWISE
									SET STEP ON
		*							lcRelType     = this.GetNodeAttributeValue(lcRelation, "Type")
		*							lcFileContent = this.ConvertFileToString(lcFileName, 13)
		*							lcFileRelsCnt = this.ConvertFileToString(lcWbXmlPath + ADDBS(JUSTPATH(lcTarget)) + "_rels\" + JUSTSTEM(lcTarget) + ".xml.rels", 13)
		*							INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, reltypeunk, target, filecontent, relscontent) ;
		*								VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_UNKNOWN, lcRelType, lcTarget, lcFileContent, lcFileRelsCnt)
							ENDCASE
						ENDIF
						IF lnRelId > lnLastRelId
							lnLastRelId = lnRelId
						ENDIF
						lcRelation = this.GetXMLNextSegment(loRelations)
					ENDDO
					this.ReleaseStringFromHeap(loWBSheets)
					this.ReleaseStringFromHeap(loRelations)
		
		*-*			Check if a cell with an inline string value was added without the relationship file strings.xml
					IF llStringsXMLAdded .AND. !llStringsXMLRels
						lnLastRelId = lnLastRelId + 1
						INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
							VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnLastRelId, RELS_TYPE_SHAREDSTRINGS, "sharedStrings.xml")
					ENDIF
		
		*-*			Save the last used relationship id
					this.SetLastId(lnWB, 0, lnLastRelId, "xl_workbook_rels")
		
		*-*			Check if any referenced sheet name was not matched to the sheet index during read for hyperlinks
					SELECT xl_hyperlinks
					SCAN FOR tgtsheet = 0
						lnTgtSheet = this.GetSheetIndex(lnWB, ALLTRIM(xl_hyperlinks.tgtsheetname))
						REPLACE xl_hyperlinks.tgtsheet WITH lnTgtSheet IN xl_hyperlinks
					ENDSCAN
		
		*-*			Get the defined name ranges
					RAISEEVENT(this, "OnShowStatusMessage", 1, 5)
					lcDefinedNames = this.GetNodeElement(lcWorkbook, 'definedNames', 1)
					IF !EMPTY(lcDefinedNames)
						lnNode  = 1
						lcDefinedName = this.GetNodeElement(lcDefinedNames, 'definedName', lnNode)
						DO WHILE !EMPTY(lcDefinedName)
							lcRange = this.GetNodeElementValue(lcDefinedName)
							IF !EMPTY(lcRange)
								lnNdx = ATC("!", lcRange)
								lcShName = LEFT(lcRange, lnNdx-1)
								IF !EMPTY(lcShName)
									lcShName = CHRTRAN(lcShName, "'", "")
									IF SEEK(BINTOC(lnWB)+UPPER(PADR(lcShName, LEN(xl_sheets.shname))), "xl_sheets", "shname")
										lcRName   = this.GetNodeAttributeValue(lcDefinedName, "name")
										lcComment = this.GetNodeAttributeValue(lcDefinedName, "comment")
										lnRangeId = CAST(this.GetNodeAttributeValue(lcDefinedName, "localSheetId") AS I)
										
										lcCellRange = SUBSTR(lcRange, lnNdx+1)
										loRange = this.ConvertRangeToColumnRowValues(lcCellRange)
		
										INSERT INTO xl_namerange (workbook, sheet, rname, scope, comment, begrow, begcol, endrow, endcol) ;
											VALUES (lnWB, xl_sheets.sheet, lcRName, lnRangeId, lcComment, loRange.BegRow, loRange.BegCol, loRange.EndRow, loRange.EndCol)
									ENDIF
								ENDIF
							ENDIF
							lnNode = lnNode + 1
							lcDefinedName = this.GetNodeElement(lcDefinedNames, 'definedName', lnNode)
						ENDDO
					ENDIF
		
					RAISEEVENT(this, "OnShowStatusMessage", 1, 6)
		
				CATCH TO loException
					SET STEP ON
					this.ErrorLevelId = 9
					RAISEEVENT(this, "OnShowErrorMessage", 9, "Invalid XLSX file - error reading data; " + loException.Message)
					this.DeleteWorkbook(lnWB)
					lnWB = 0
		
				FINALLY
					TRY
						loFS = CREATEOBJECT("Scripting.FileSystemObject")
						loDir = loFS.GetFolder(lcTempPath)
						loDir.Delete()
		
					CATCH TO loException
						SET STEP ON
					ENDTRY
					RAISEEVENT(this, "OnShowStatusMessage", 1, -1)
				ENDTRY
			ENDIF
			this.RestoreVFPSettings()
		ENDIF
		RETURN lnWB
	ENDPROC

	PROCEDURE OpenXlsxWorkbookSheet		&& Opens a selected worksheet in a XLXS workbook
		LPARAMETERS tcFileName, txSheet, tlForceTextFormat, tlReadGraphicData
		LOCAL lnWB, lcTempPath, lcRelations, lnNode, lcTgtFile, lnTgtSheet, lcTgtSheet, loRange, lcContType, lcFileRelsCnt, lnShId, llFileOpen
		LOCAL lnRelId, lcRelType, lcTarget, lcWbXmlPath, lcRelation, lcWorkbook, lcSheet, lcShName, lcFileContent, loRelations, lcRelId, loWBSheets, lnSec
		LOCAL loException, loFS, lcDefinedNames, lcDefinedName, lcRName, lcComment, lnRangeId, lcRange, lnNdx, lnShState, lcFileExt, lcRelationships, lcShState
		LOCAL lcCellRange, loDir, lnLastRelId, lcWBSheets, lcContTypes, lcDefType, llProcessSheet, lcTmpSheet
		LOCAL ARRAY laTemp[1]
		DO CASE
			CASE PCOUNT() = 0
				this.ErrorLevelId = 1
				RAISEEVENT(this, "OnShowErrorMessage", 1, "Invalid call to OpenXlsxWorkbook() method; must include file name to open and sheet")
				RETURN 0
		
			CASE PCOUNT() = 1
				this.ErrorLevelId = 1
				RAISEEVENT(this, "OnShowErrorMessage", 1, "Invalid call to OpenXlsxWorkbook() method; must include sheet to process")
				RETURN 0
		
			CASE PCOUNT() = 2
				tlForceTextFormat = False
				tlReadGraphicData = True
		
			CASE PCOUNT() = 3
				tlReadGraphicData = True
		ENDCASE
		lnWB = 0
		IF ADIR(laTemp, tcFileName) > 0
		*-*	Open the Workbook as an archive (zip) file and extract contents
			lcTempPath = ADDBS(SYS(2023)) + SYS(2015)
			llFileOpen = this.OpenXlsxFileAsZip(tcFileName, lcTempPath)
			IF !llFileOpen
				RETURN 0
			ENDIF
		
		*-*	Save system settings
			this.SaveVFPSettings()
		
		*-*	Add to the workbook table
			lnWB = this.CreateWorkbookEx(tcFileName)
			IF lnWB > 0
		*-*		Verify all required XML files are present
				lcWbXmlPath = IIF(RIGHT(lcTempPath, 1) = "\", LEFT(lcTempPath, LEN(lcTempPath)-1), lcTempPath)
				IF ADIR(laTemp, lcWbXmlPath + "\xl\workbook.xml") = 0
					this.ErrorLevelId = 3
					RAISEEVENT(this, "OnShowErrorMessage", 3, "Invalid XLSX file - missing workbook.xml")
					this.DeleteWorkbook(lnWB)
					this.RestoreVFPSettings()
					RETURN 0		
				ENDIF
				IF ADIR(laTemp, lcWbXmlPath + "\xl\_rels\workbook.xml.rels") = 0
					this.ErrorLevelId = 4
					RAISEEVENT(this, "OnShowErrorMessage", 4, "Invalid XLSX file - missing workbook.xml.rels")
					this.DeleteWorkbook(lnWB)
					this.RestoreVFPSettings()
					RETURN 0
				ENDIF
				IF ADIR(laTemp, lcWbXmlPath + "\xl\styles.xml") = 0
					this.ErrorLevelId = 5
					RAISEEVENT(this, "OnShowErrorMessage", 5, "Invalid XLSX file - missing styles.xml")
					this.DeleteWorkbook(lnWB)
					this.RestoreVFPSettings()
					RETURN 0
				ENDIF
		
		*-*		Process the workbook
				RAISEEVENT(this, "OnShowStatusMessage", 1, 1, 6)
				TRY
		*-*			Get the default content types
					lcContTypes = FILETOSTR(lcWbXmlPath + "\[Content_Types].xml")
					lnNode = 1
					lcDefType = this.GetNodeElement(lcContTypes, 'Default', lnNode)
					DO WHILE !EMPTY(lcDefType)
						lcFileExt  = this.GetNodeAttributeValue(lcDefType, "Extension")
						lcContType = this.GetNodeAttributeValue(lcDefType, "ContentType")
						
						IF tlReadGraphicData
							INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (lnWB, lcFileExt, lcContType)
						ELSE
							IF lcContType != "image"
								INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (lnWB, lcFileExt, lcContType)
							ENDIF
						ENDIF
		
						lnNode = lnNode + 1
						lcDefType = this.GetNodeElement(lcContTypes, 'Default', lnNode)
					ENDDO
		
		*-*			Set base path to workbook files
					lcWbXmlPath = lcWbXmlPath + "\xl\"
		
		*-*			Load the workbook files into memory for processing
					lcWorkbook = FILETOSTR(lcWbXmlPath + "workbook.xml")
					lcWBSheets = this.GetNodeElement(lcWorkbook, 'sheets', 1, False, 1)
					loWBSheets = this.AddStringToHeap(lcWBSheets)
		
		*-*			Get the workbook settings in the workbook.xml file
		*-*				Currently not adding calc chain to workbook for restoring; has problems if sheet ids change or sheets removed/added
		*	 			lcCalcPr = this.GetNodeElement(lcWorkbook, 'calcPr', 1, False, 1)
		*	 			lnCalcPr = CAST(this.GetNodeAttributeValue(lcCalcPr, "calcId") AS I)
		*	 			IF lnCalcPr > 0
		*	 				UPDATE xl_workbooks SET calcid = lnCalcPr WHERE workbook = lnWB
		*	 			ENDIF
		
		*-*			Get the defined relationship files for this workbook; process each file
					RAISEEVENT(this, "OnShowStatusMessage", 1, 3)
					lcRelations = FILETOSTR(lcWbXmlPath + "_rels\workbook.xml.rels")
					loRelations = this.AddStringToHeap(lcRelations)
					lcRelation  = this.GetXMLFirstSegment(loRelations, 'Relationship')
		
					lnLastRelId = 0
					DO WHILE !EMPTY(lcRelation)
						lcRelId   = this.GetNodeAttributeValue(lcRelation, "Id")
						lnRelId   = CAST(SUBSTR(lcRelId, 4) AS I)
						lcTarget  = this.GetNodeAttributeValue(lcRelation, "Target")
						lcRelType = this.GetNodeAttributeValue(lcRelation, "Type")
						lcRelType = SUBSTR(lcRelType, RATC("/", lcRelType)+1)
						DO CASE
							CASE lcRelType = "calcChain"
		*						lcFileContent = FILETOSTR(lcWbXmlPath + lcTarget)
		*						INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent) ;
		*							VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_CALCCHAIN, lcTarget, lcFileContent)
		
							CASE lcRelType = "externalLink"
								lcFileContent = this.ConvertFileToString(lcWbXmlPath + lcTarget, 13)
								lcFileRelsCnt = this.ConvertFileToString(lcWbXmlPath + ADDBS(JUSTPATH(lcTarget)) + "_rels\" + JUSTSTEM(lcTarget) + ".xml.rels", 13)
								INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent, relscontent) ;
									VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_EXTNLINK, lcTarget, lcFileContent, lcFileRelsCnt)
		
							CASE lcRelType = "sharedStrings"
								lnSec = SECONDS()
								IF !this.ReadSharedStringsXML(lcWbXmlPath + lcTarget, lnWB)
									this.ErrorLevelId = 7
									RAISEEVENT(this, "OnShowErrorMessage", 7, "Invalid XLSX file - error during shared string loading")
									THROW
								ENDIF
								INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
									VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_SHAREDSTRINGS, lcTarget)
		*-*							IF this.Debug
		*-*								? "Read sharedStrings: " + TRANSFORM(SECONDS() - lnSec)
		*-*							ENDIF
		
							CASE lcRelType = "styles"
								lnSec = SECONDS()
								this.ReadStylesXML(lcWbXmlPath + lcTarget, lnWB)
								INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
									VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_STYLES, lcTarget)
		*-*							IF this.Debug
		*-*								? "Read styles: " + TRANSFORM(SECONDS() - lnSec)
		*-*							ENDIF
		
							CASE lcRelType = "theme"
								lcFileContent = FILETOSTR(lcWbXmlPath + lcTarget)
								INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, filecontent) ;
									VALUES (lnWB, 0, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_THEME, lcTarget, lcFileContent)
		
							CASE lcRelType = "worksheet"
								lnSec   = SECONDS()
								lcSheet = ""
								lcTmpSheet = this.GetXMLFirstSegment(loWBSheets, 'sheet')
								DO WHILE !EMPTY(lcTmpSheet)
									IF ATC(lcRelId, lcTmpSheet) > 0
										lcSheet = lcTmpSheet
										EXIT
									ENDIF
									lcTmpSheet = this.GetXMLNextSegment(loWBSheets)
								ENDDO
								IF EMPTY(lcSheet)
									SET STEP ON
									this.ErrorLevelId = 8
									RAISEEVENT(this, "OnShowErrorMessage", 8, "Invalid XLSX file - missing sheet or invalid sheet: " + lcTarget)
									this.DeleteWorkbook(lnWB)
									THROW
								ENDIF
								lnShId   = CAST(CHRTRAN(lcTarget, CHRTRAN(lcTarget, '1234567890', ''), '') AS I)
								lcShName = this.GetNodeAttributeValue(lcSheet, "name")
								DO CASE
									CASE VARTYPE(txSheet) = "C"
										IF UPPER(lcShName) == UPPER(txSheet)
											llProcessSheet = True
										ELSE
											llProcessSheet = False
										ENDIF
		
									CASE VARTYPE(txSheet) = "N"
										IF lnShId = txSheet
											llProcessSheet = True
										ELSE
											llProcessSheet = False
										ENDIF
		
									OTHERWISE
								ENDCASE
								IF llProcessSheet
									lcShState = this.GetNodeAttributeValue(lcSheet, "state")
									DO CASE
										CASE lcShState = "visible"
											lnShState = VISIBLE_SHEET_STATE
			
										CASE lcShState = "hidden"
											lnShState = HIDDEN_SHEET_STATE
			
										CASE lcShState = "veryHidden"
											lnShState = VERYHIDDEN_SHEET_STATE
			
										OTHERWISE
											lnShState = VISIBLE_SHEET_STATE
									ENDCASE
									this.ReadSheetXML(lcWbXmlPath + lcTarget, lnWB, 1, lcShName, lnShState, tlForceTextFormat, tlReadGraphicData)
									lcTarget = CHRTRAN(lcTarget, '1234567890', '')
									lcTarget = STUFF(lcTarget, ATC('.', lcTarget), 0, '1')
									INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target) ;
										VALUES (lnWB, 1, RELS_SRC_WORKBOOK, lnRelId, RELS_TYPE_WORKSHEET, lcTarget)
			
									this.SetLastId(lnWB, 0, 1, "xl_sheets")
		*-*								IF this.Debug
		*-*									? "Read worksheet" + TRANSFORM(lnShId) + ": " + TRANSFORM(SECONDS() - lnSec)
		*-*								ENDIF
								ENDIF
		
							OTHERWISE
								SET STEP ON
						ENDCASE
						IF lnRelId > lnLastRelId
							lnLastRelId = lnRelId
						ENDIF
						lcRelation = this.GetXMLNextSegment(loRelations)
					ENDDO
					this.ReleaseStringFromHeap(loWBSheets)
					this.ReleaseStringFromHeap(loRelations)
					this.SetLastId(lnWB, 0, lnLastRelId, "xl_workbook_rels")
		
		*-*			Check if any referenced sheet name was not matched to the sheet index during read for hyperlinks
					SELECT xl_hyperlinks
					SCAN FOR tgtsheet = 0
						lnTgtSheet = this.GetSheetIndex(lnWB, ALLTRIM(xl_hyperlinks.tgtsheetname))
						REPLACE xl_hyperlinks.tgtsheet WITH lnTgtSheet IN xl_hyperlinks
					ENDSCAN
		
		*-*			Get the defined name ranges
					RAISEEVENT(this, "OnShowStatusMessage", 1, 5)
					lcDefinedNames = this.GetNodeElement(lcWorkbook, 'definedNames', 1)
					IF !EMPTY(lcDefinedNames)
						lnNode  = 1
						lcDefinedName = this.GetNodeElement(lcDefinedNames, 'definedName', lnNode)
						DO WHILE !EMPTY(lcDefinedName)
							lcRange = this.GetNodeElementValue(lcDefinedName)
							IF !EMPTY(lcRange)
								lnNdx = ATC("!", lcRange)
								lcShName = LEFT(lcRange, lnNdx-1)
								IF !EMPTY(lcShName)
									lcShName = CHRTRAN(lcShName, "'", "")
									IF SEEK(BINTOC(lnWB)+UPPER(PADR(lcShName, LEN(xl_sheets.shname))), "xl_sheets", "shname")
										lcRName   = this.GetNodeAttributeValue(lcDefinedName, "name")
										lcComment = this.GetNodeAttributeValue(lcDefinedName, "comment")
										lnRangeId = CAST(this.GetNodeAttributeValue(lcDefinedName, "localSheetId") AS I)
										
										lcCellRange = SUBSTR(lcRange, lnNdx+1)
										loRange = this.ConvertRangeToColumnRowValues(lcCellRange)
		
										INSERT INTO xl_namerange (workbook, sheet, rname, scope, comment, begrow, begcol, endrow, endcol) ;
											VALUES (lnWB, xl_sheets.sheet, lcRName, lnRangeId, lcComment, loRange.BegRow, loRange.BegCol, loRange.EndRow, loRange.EndCol)
									ENDIF
								ENDIF
							ENDIF
							lnNode = lnNode + 1
							lcDefinedName = this.GetNodeElement(lcDefinedNames, 'definedName', lnNode)
						ENDDO
					ENDIF
		
					RAISEEVENT(this, "OnShowStatusMessage", 1, 6)
		
				CATCH TO loException
					SET STEP ON
					this.ErrorLevelId = 9
					RAISEEVENT(this, "OnShowErrorMessage", 9, "Invalid XLSX file - error reading data; " + loException.Message)
					this.DeleteWorkbook(lnWB)
					lnWB = 0
		
				FINALLY
					TRY
						loFS = CREATEOBJECT("Scripting.FileSystemObject")
						loDir = loFS.GetFolder(lcTempPath)
						loDir.Delete()
		
					CATCH TO loException
						SET STEP ON
					ENDTRY
					RAISEEVENT(this, "OnShowStatusMessage", 1, -1)
				ENDTRY
			ENDIF
			this.RestoreVFPSettings()
		ENDIF
		RETURN lnWB
	ENDPROC

	PROCEDURE ParseString		&& Parses a string based on a specified delimiter
		LPARAMETERS tcText, tnPos, tcDelimiter
		LOCAL lcWord, lnCnt, lnBeg, lnEnd
		lnCnt = OCCURS(tcDelimiter, tcText) + 1
		IF tnPos <= lnCnt
			DO CASE
				CASE lnCnt = 0
					lcWord = tcText
		
				CASE tnPos = 1
					lcWord = LEFT(tcText, ATC(tcDelimiter, tcText)-1)
		
				CASE tnPos = lnCnt
					lcWord = SUBSTR(tcText, RATC(tcDelimiter, tcText)+1)
		
				OTHERWISE
					lnBeg = ATC(tcDelimiter, tcText, tnPos-1) + 1
					lnEnd = ATC(tcDelimiter, tcText, tnPos)
					lcWord = SUBSTR(tcText, lnBeg, lnEnd-lnBeg)
			ENDCASE
			RETURN lcWord
		ELSE
			RETURN ""
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE ReadCellValueFormat		&& Reads the cell value and format for a sheet
		LPARAMETERS tnWB, tnSheet, tcColumn, tnCellRow, tnCellCol
		LOCAL lcCellType, lnNode, lcBaseName, lcFormatCode, lnNdx, loCell, lcWholeNbr, lcDecNbr
		LOCAL lcPosFormat, lcNegFormat, lcZerFormat, lcInLineTxt, lcChild, lcRefBeg, lcRefEnd, lcRelative
		LOCAL lcFormulaRef, lcFormula
		loCell = CREATEOBJECT("Empty")
		ADDPROPERTY(loCell, "CellVal", "")
		ADDPROPERTY(loCell, "DataType", DATA_TYPE_NONE)
		ADDPROPERTY(loCell, "StringNdx", 0)
		ADDPROPERTY(loCell, "CellXfsId", 0)
		ADDPROPERTY(loCell, "FormulaType", "")
		ADDPROPERTY(loCell, "FormulaSi", -1)
		
		lcCellType = this.GetNodeAttributeValue(tcColumn, "t")
		IF lcCellType = "e"                                                      && Cell contains an error, do not process
			loCell.CellXfsId = CAST(this.GetNodeAttributeValue(tcColumn, "s") AS I)
		ELSE
		*-*	Check for formula expression
			lcChild = this.GetNodeElement(tcColumn, "f", 1, False, 5)
			IF !EMPTY(lcChild)
				lcCellType   = "f"
				lcFormula    = this.GetNodeElementValue(lcChild)
				lcFormulaRef = this.GetNodeAttributeValue(lcChild, "ref")
				loCell.FormulaType = this.GetNodeAttributeValue(lcChild, "t")
				IF loCell.FormulaType = "shared"
					loCell.FormulaSi = CAST(this.GetNodeAttributeValue(lcChild, "si") AS INTEGER)
					lcRefBeg   = this.GetColumnInColRowPair(GETWORDNUM(lcFormulaRef, 1, ":"))
					lcRefEnd   = this.GetColumnInColRowPair(GETWORDNUM(lcFormulaRef, 2, ":"))
					lcRelative = IIF(lcRefBeg == lcRefEnd, SHARED_FORMULA_ROW, SHARED_FORMULA_COLUMN)
				ELSE
					lcRelative = ""
					loCell.FormulaSi = -1
				ENDIF
				INSERT INTO xl_formulas (workbook, sheet, cellrow, cellcol, formulatype, si, ref, formrel, cellformula) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, loCell.FormulaType, loCell.FormulaSi, lcFormulaRef, lcRelative, lcFormula)
			ENDIF
		
		*-*	Check for value assigned to cell
			lcChild = this.GetNodeElement(tcColumn, "v" , 1, False, 5)
			IF !EMPTY(lcChild)
				loCell.CellVal = this.GetNodeElementValue(lcChild)
			ENDIF
		
		*-*	Check for inline text assigned to cell
			lcChild = this.GetNodeElement(tcColumn, "is", 1, False, 5)
			IF !EMPTY(lcChild)
				lcInLineTxt = this.GetNodeElement(lcChild, 't', 1)
				loCell.CellVal = this.GetNodeElementValue(lcInLineTxt)
			ENDIF
		
			loCell.CellXfsId = CAST(this.GetNodeAttributeValue(tcColumn, "s") AS I)
			DO CASE
				CASE lcCellType == "f"
					loCell.DataType = DATA_TYPE_FORMULA
		
				CASE lcCellType == "s" .OR. lcCellType == "str"
					loCell.StringNdx = CAST(loCell.CellVal AS I)
					loCell.CellVal   = "StringValue"
					loCell.DataType  = DATA_TYPE_CHAR
			
				CASE lcCellType == "inlineStr"
					loCell.CellVal   = this.GetStringXML(loCell.CellVal)
					loCell.StringNdx = this.AddStringValue(tnWB, loCell.CellVal, False)
					loCell.CellVal   = "StringValue"
					loCell.DataType  = DATA_TYPE_CHAR
		
				CASE EMPTY(loCell.CellVal)
					RETURN loCell
		
				OTHERWISE
		*-*			Determine the data type of the value based on assigned format
					IF SEEK(BINTOC(tnWB)+BINTOC(loCell.CellXfsId), "xl_cellxfs", "id")
						DO CASE
							CASE INLIST(xl_cellxfs.numFmtId, 1, 3, 37, 38)                           && Integer format
								loCell.DataType = DATA_TYPE_INT
								
							CASE INLIST(xl_cellxfs.numFmtId, 2, 4, 7, 8, 11, 12, 13, 39, 40)         && Float format
								loCell.DataType = DATA_TYPE_FLOAT
						
							CASE INLIST(xl_cellxfs.numFmtId, 9, 10)                                  && Numeric percent format
								loCell.DataType = DATA_TYPE_FLOAT
				
							CASE INLIST(xl_cellxfs.numFmtId, 14, 15, 16, 17)                         && Date format
								DO CASE
									CASE ATC(".", loCell.CellVal) = 0 .OR. RIGHT(loCell.CellVal, 1) = "."
										loCell.DataType = DATA_TYPE_DATE
									
									CASE ATC(".", loCell.CellVal) > 0
										lnNdx = ATC(".", loCell.CellVal)
										lcWholeNbr = LEFT(loCell.CellVal, lnNdx-1)
										lcDecNbr   = SUBSTR(loCell.CellVal, lnNdx+1)
										IF LEN(lcDecNbr) > 0
											loCell.CellVal  = lcWholeNbr + "." + PADR(lcDecNbr, 17, "0")
											loCell.DataType = DATA_TYPE_DATETIME
										ELSE	
											loCell.CellVal  = lcWholeNbr
											loCell.DataType = DATA_TYPE_DATE
										ENDIF
								ENDCASE
		
							CASE INLIST(xl_cellxfs.numFmtId, 18, 19, 20, 21, 45, 46)                 && Time format
								DO CASE
									CASE ATC("E-", loCell.CellVal) > 0
										loCell.CellVal  = PADR(TRANSFORM(EVAL(loCell.CellVal)), 19, "0")
										loCell.DataType = DATA_TYPE_TIME
		
									CASE ATC("E", loCell.CellVal) > 0
										loCell.CellVal = TRANSFORM(EVAL(loCell.CellVal))
										lnNdx = ATC(".", loCell.CellVal)
										lcWholeNbr = LEFT(loCell.CellVal, lnNdx-1)
										lcDecNbr   = SUBSTR(loCell.CellVal, lnNdx+1)
										loCell.CellVal  = lcWholeNbr + "." + PADR(lcDecNbr, 17, "0")
										loCell.DataType = DATA_TYPE_DATETIME
		
									CASE LEFT(loCell.CellVal, 2) = "0."
										lcDecNbr = SUBSTR(loCell.CellVal, 3)
										loCell.CellVal  = "0." + PADR(lcDecNbr, 17, "0")
										loCell.DataType = DATA_TYPE_TIME
		
									OTHERWISE
										lnNdx = ATC(".", loCell.CellVal)
										lcWholeNbr = LEFT(loCell.CellVal, lnNdx-1)
										lcDecNbr   = SUBSTR(loCell.CellVal, lnNdx+1)
										loCell.CellVal  = lcWholeNbr + "." + PADR(lcDecNbr, 17, "0")
										loCell.DataType = DATA_TYPE_FLOAT
								ENDCASE
			
							CASE INLIST(xl_cellxfs.numFmtId, 22, 29, 30, 31, 32, 33, 34)             && Date-time format
								lnNdx = ATC(".", loCell.CellVal)
								lcWholeNbr = IIF(lnNdx=0, loCell.CellVal, LEFT(loCell.CellVal, lnNdx-1))     && Bug fix provide by Dan Goodwin from VFPx
								lcDecNbr   = IIF(lnNdx=0, "", SUBSTR(loCell.CellVal, lnNdx+1))                     && Bug fix provide by Dan Goodwin from VFPx
								loCell.CellVal  = lcWholeNbr + "." + PADR(lcDecNbr, 17, "0")
								loCell.DataType = DATA_TYPE_DATETIME
		
							CASE xl_cellxfs.numFmtId = 49                                            && Numeric formatted as text
								loCell.DataType = DATA_TYPE_TEXT
		
							CASE SEEK(BINTOC(tnWB)+BINTOC(xl_cellxfs.numFmtId), "xl_numfmts", "id")  && Determine based on custom format
								lcFormatCode = ALLTRIM(xl_numfmts.formatcode)
								DO CASE
									CASE ATC("h:m", lcFormatCode) > 0 .OR. ATC("m:s", lcFormatCode) > 0 .OR. ATC("AM/PM", lcFormatCode) > 0 .OR. ATC("A/P", lcFormatCode) > 0
										loCell.DataType = DATA_TYPE_DATETIME
		
									CASE ATC("yy", lcFormatCode) > 0 .OR. ATC("d\-m", lcFormatCode) > 0
										loCell.DataType = DATA_TYPE_DATE
		
									OTHERWISE
										lnNdx = ATC(".", lcFormatCode)
										IF lnNdx > 0
											loCell.DataType = DATA_TYPE_FLOAT
											lnNdx = lnNdx + 1
											DO WHILE lnNdx < LEN(lcFormatCode)
												IF SUBSTR(lcFormatCode, lnNdx, 1) != "0"
													EXIT
												ENDIF
												lnNdx = lnNdx + 1
											ENDDO
										ELSE
											loCell.DataType = DATA_TYPE_INT
										ENDIF
								ENDCASE
		
							OTHERWISE
								DO CASE
									CASE ISNULL(loCell.CellVal) .OR. EMPTY(loCell.CellVal)
										loCell.DataType = DATA_TYPE_NONE
		
									CASE ATC(".", loCell.CellVal) > 0
										loCell.DataType = DATA_TYPE_FLOAT
		
									OTHERWISE
										loCell.DataType = DATA_TYPE_INT
								ENDCASE
						ENDCASE
					ELSE
						DO CASE
							CASE ISNULL(loCell.CellVal) .OR. EMPTY(loCell.CellVal)
								loCell.CellVal  = ""
								loCell.DataType = DATA_TYPE_NONE
		
							CASE ATC(".", loCell.CellVal) > 0
								loCell.DataType = DATA_TYPE_FLOAT
		
							OTHERWISE
								loCell.DataType = DATA_TYPE_INT
						ENDCASE
					ENDIF
			ENDCASE
		ENDIF
		RETURN loCell
	ENDPROC

	PROTECTED PROCEDURE ReadCommentXML		&& Reads the comment definitions for the sheet
		LPARAMETERS tnWB, tnSheet, tcCommentFile
		LOCAL lcCommentXML, loException, loXML
		TRY
		*-*		lcCommentXML = FILETOSTR(tcCommentFile)
		*-*	
		*-*		IF EMPTY(lcCommentXML)
		*-*			THROW "No comment definitions in Sheet " + TRANSFORM(tnSheet)
		*-*		ELSE
		*-*	*-*		Create XML in memory
		*-*			loXML = this.AddStringToHeap(lcCommentXML)
		*-*	
		*-*	*-*		Process xdr:absoluteAnchor segments
		*-*			lcAbsoluteAnchor = this.GetXMLFirstSegment(loXML, 'xdr:absoluteAnchor')
		*-*			DO WHILE !EMPTY(lcAbsoluteAnchor)
		*-*	
		*-*	
		*-*				lcAbsoluteAnchor = this.GetXMLNextSegment(loXML)
		*-*			ENDDO
		*-*			this.ReleaseStringFromHeap(loXML)
		*-*		ENDIF
		
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
	ENDPROC

	PROTECTED PROCEDURE ReadDrawingXML		&& Reads the drawing (image) xml for associated sheet
		LPARAMETERS tnWB, tnSheet, tcDwgFile, tnRelId
		LOCAL lcDrawingXML, loException, lcAnchorXml, lcXdrFrom, lnBegCol, lnBegColOff, lnBegRow, lnBegRowOff
		LOCAL lcXdrTo, lnEndCol, lnEndColOff, lnEndRow, lnEndRowOff, loXdrPic, lcXdrPic, lcFileExtn
		LOCAL lcXdrExt, lnX, lnY, lnCX, lnCY, lcXdrSp, loDrawing
		
		TRY
			lcDrawingXML = FILETOSTR(tcDwgFile)
		
			IF EMPTY(lcDrawingXML)
				THROW "No drawing definitions in Sheet " + TRANSFORM(tnSheet)
			ELSE
		*-*		Load the drawing.xml into memory object
				loDrawing = this.LoadDrawingXml(lcDrawingXML)
		
		*-*		Get the drawing information positioned by absoluteAnchor
				FOR lnNdx=1 TO loDrawing.absoluteAnchor.Count
					lcAnchorXml = loDrawing.absoluteAnchor.List[lnNdx]
		
					lcXdrExt = this.GetNodeElement(lcAnchorXml, 'xdr:ext', 1)
					lnX      = CAST(this.GetNodeAttributeValue(lcXdrExt, "x") AS I)
					lnY      = CAST(this.GetNodeAttributeValue(lcXdrExt, "y") AS I)
		
					lcXdrExt = this.GetNodeElement(lcAnchorXml, 'xdr:ext', 2)
					lnCX     = CAST(this.GetNodeAttributeValue(lcXdrExt, "cx") AS I)
					lnCY     = CAST(this.GetNodeAttributeValue(lcXdrExt, "cy") AS I)
		
					lcXdrPic = this.GetNodeElement(lcAnchorXml, 'xdr:pic', 1, True, 1)
					IF EMPTY(lcXdrPic)
						lcXdrSp = this.GetNodeElement(lcAnchorXml, 'xdr:sp', 1, True, 1)
						IF !EMPTY(lcXdrSp)
		*-* TODO: Not yet supported for shapes
						ENDIF
					ELSE
						loXdrPic = this.GetDwgPicDefinition(lcXdrPic, tcDwgFile)
		
						INSERT INTO xl_images (workbook, sheet, relid, anchor, begcol, begrow, cx, cy) ;
							VALUES (tnWB, tnSheet, loXdrPic.RelId, IMAGE_ANCHOR_TYPE_ABS, lnX, lnY, lnCX, lnCY)
		
						INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL, globalid, filecontent) ;
							VALUES (tnWB, tnSheet, RELS_SRC_DRAWING, loXdrPic.RelId, RELS_TYPE_IMAGE, JUSTFNAME(loXdrPic.ImageFile), False, loXdrPic.GblId, ;
							this.ConvertFileToString(loXdrPic.ImageFile, 13))
						this.SetLastId(tnWB, tnSheet, loXdrPic.RelId, "xl_drawing_rels")
		
						IF !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+RELS_TYPE_DRAWING, "xl_relationships", "reltype")
							INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL) ;
								VALUES (tnWB, tnSheet, RELS_SRC_SHEET, tnRelId, RELS_TYPE_DRAWING, "../drawings/drawing" + TRANSFORM(tnSheet) + ".xml", False)
							this.SetLastId(tnWB, tnSheet, tnRelId, "xl_sheet_rels")
						ENDIF
						this.SetLastId(tnWB, tnSheet, loXdrPic.GblId, "global")
		
						lcFileExtn = LOWER(JUSTEXT(loXdrPic.ImageFile))
						IF !SEEK(BINTOC(tnWB)+lcFileExtn, "xl_contenttypes", "extension")
							INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (tnWB, lcFileExtn, "image/" + lcFileExtn)
						ENDIF
					ENDIF
				ENDFOR
		
		*-*		Get the drawing information positioned by oneCellAnchor
				FOR lnNdx=1 TO loDrawing.oneCellAnchor.Count
					lcAnchorXml = loDrawing.oneCellAnchor.List[lnNdx]
		
					lcXdrFrom   = this.GetNodeElement(lcAnchorXml, 'xdr:from', 1, True, 1)
					lnBegCol    = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:col', 1, True, 1) AS I)
					lnBegColOff = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:colOff', 1, True, 1) AS I)
					lnBegRow    = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:row', 1, True, 1) AS I)
					lnBegRowOff = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:rowOff', 1, True, 1) AS I)
		
					lcXdrPic = this.GetNodeElement(lcAnchorXml, 'xdr:pic', 1, True, 1)
					IF EMPTY(lcXdrPic)
						lcXdrSp = this.GetNodeElement(lcAnchorXml, 'xdr:sp', 1, True, 1)
						IF !EMPTY(lcXdrSp)
		*-* TODO: Not yet supported for shapes
						ENDIF
					ELSE
						loXdrPic = this.GetDwgPicDefinition(lcXdrPic, tcDwgFile)
			
						INSERT INTO xl_images (workbook, sheet, relid, anchor, begcol, begcoloff, begrow, begrowoff, cx, cy) ;
							VALUES (tnWB, tnSheet, loXdrPic.RelId, IMAGE_ANCHOR_TYPE_ONE, lnBegCol, lnBegColOff, lnBegRow, lnBegRowOff, loXdrPic.ImgWidth, loXdrPic.ImgHeight)
			
						INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL, globalid, filecontent) ;
							VALUES (tnWB, tnSheet, RELS_SRC_DRAWING, loXdrPic.RelId, RELS_TYPE_IMAGE, JUSTFNAME(loXdrPic.ImageFile), False, loXdrPic.GblId, ;
									this.ConvertFileToString(loXdrPic.ImageFile, 13))
						this.SetLastId(tnWB, tnSheet, loXdrPic.RelId, "xl_drawing_rels")
			
						IF !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+RELS_TYPE_DRAWING, "xl_relationships", "reltype")
							INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL) ;
								VALUES (tnWB, tnSheet, RELS_SRC_SHEET, tnRelId, RELS_TYPE_DRAWING, "../drawings/drawing" + TRANSFORM(tnSheet) + ".xml", False)
							this.SetLastId(tnWB, tnSheet, tnRelId, "xl_sheet_rels")
						ENDIF
						this.SetLastId(tnWB, tnSheet, loXdrPic.GblId, "global")
			
						lcFileExtn = LOWER(JUSTEXT(loXdrPic.ImageFile))
						IF !SEEK(BINTOC(tnWB)+lcFileExtn, "xl_contenttypes", "extension")
							INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (tnWB, lcFileExtn, "image/" + lcFileExtn)
						ENDIF
					ENDIF
				ENDFOR
		
		*-*		Get the drawing information positioned by twoCellAnchor
				FOR lnNdx=1 TO loDrawing.twoCellAnchor.Count
					lcAnchorXml = loDrawing.twoCellAnchor.List[lnNdx]
		
					lcXdrFrom   = this.GetNodeElement(lcAnchorXml, 'xdr:from', 1, True, 1)
					lnBegCol    = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:col', 1, True, 1) AS I)
					lnBegColOff = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:colOff', 1, True, 1) AS I)
					lnBegRow    = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:row', 1, True, 1) AS I)
					lnBegRowOff = CAST(this.GetNodeElement(lcXdrFrom, 'xdr:rowOff', 1, True, 1) AS I)
		
					lcXdrTo     = this.GetNodeElement(lcAnchorXml, 'xdr:to', 1, True)
					lnEndCol    = CAST(this.GetNodeElement(lcXdrTo, 'xdr:col', 1, True, 1) AS I)
					lnEndColOff = CAST(this.GetNodeElement(lcXdrTo, 'xdr:colOff', 1, True, 1) AS I)
					lnEndRow    = CAST(this.GetNodeElement(lcXdrTo, 'xdr:row', 1, True, 1) AS I)
					lnEndRowOff = CAST(this.GetNodeElement(lcXdrTo, 'xdr:rowOff', 1, True, 1) AS I)
		
					lcXdrPic = this.GetNodeElement(lcAnchorXml, 'xdr:pic', 1, True, 1)
					IF EMPTY(lcXdrPic)
						lcXdrSp = this.GetNodeElement(lcAnchorXml, 'xdr:sp', 1, True, 1)
						IF !EMPTY(lcXdrSp)
		*-* TODO: Not yet supported for shapes
						ENDIF
					ELSE
						lcEditAs = this.GetNodeAttributeValue(lcAnchorXml, "editAs")
						loXdrPic = this.GetDwgPicDefinition(lcXdrPic, tcDwgFile)
		
						INSERT INTO xl_images (workbook, sheet, relid, anchor, imgmove, begcol, begcoloff, begrow, begrowoff, endcol, endcoloff, endrow, endrowoff, cx, cy) ;
							VALUES (tnWB, tnSheet, loXdrPic.RelId, IMAGE_ANCHOR_TYPE_TWO, lcEditAs, lnBegCol, lnBegColOff, lnBegRow, lnBegRowOff, lnEndCol, lnEndColOff, ;
							lnEndRow, lnEndRowOff, loXdrPic.ImgWidth, loXdrPic.ImgHeight)
		
						INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL, globalid, filecontent) ;
							VALUES (tnWB, tnSheet, RELS_SRC_DRAWING, loXdrPic.RelId, RELS_TYPE_IMAGE, JUSTFNAME(loXdrPic.ImageFile), False, loXdrPic.GblId, ;
							this.ConvertFileToString(loXdrPic.ImageFile, 13))
		
						this.SetLastId(tnWB, tnSheet, loXdrPic.RelId, "xl_drawing_rels")
		
						IF !SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+RELS_TYPE_DRAWING, "xl_relationships", "reltype")
							INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL) ;
								VALUES (tnWB, tnSheet, RELS_SRC_SHEET, tnRelId, RELS_TYPE_DRAWING, "../drawings/drawing" + TRANSFORM(tnSheet) + ".xml", False)
		
							this.SetLastId(tnWB, tnSheet, tnRelId, "xl_sheet_rels")
						ENDIF
						this.SetLastId(tnWB, tnSheet, loXdrPic.GblId, "global")
		
						lcFileExtn = LOWER(JUSTEXT(loXdrPic.ImageFile))
						IF !SEEK(BINTOC(tnWB)+lcFileExtn, "xl_contenttypes", "extension")
							INSERT INTO xl_contenttypes (workbook, extension, contenttype) VALUES (tnWB, lcFileExtn, "image/" + lcFileExtn)
						ENDIF
					ENDIF
				ENDFOR
			ENDIF
		
		CATCH TO loException
			SET STEP ON
		ENDTRY
	ENDPROC

	PROTECTED PROCEDURE ReadExternalRefXML		&& Reads the External References file
		LPARAMETERS tnWB, tcFileName, tnRelId
		LOCAL lcExternalLink, lcSheetNames, lnNode, lcSheetName, lcShName
		
		*-*	Open the external file for processing
		lcExternalLink = FILETOSTR(tcFileName)
		
		*-*	Get the sheet names
		lcSheetNames = this.GetNodeElement(lcExternalLink, 'sheetNames', 1)
		IF !EMPTY(lcSheetNames)
			lnNode = 1
			lcSheetName = this.GetNodeElement(lcSheetNames, 'sheetName', lnNode)
			DO WHILE !EMPTY(lcSheetName)
				lcShName = this.GetNodeAttributeValue(lcSheetName, "val")
		
				INSERT INTO xl_extsheets (workbook, extid, relid, sheetname) VALUES (tnWB, lnNode, tnRelId, lcShName)
		
				lnNode = lnNode + 1
				lcSheetName = this.GetNodeElement(lcSheetNames, 'sheetName', lnNode)
			ENDDO
		
		*-*	Get each external sheet detail
			lnNode = 1
			lcSheetData = this.GetNodeElement(lcExternalLink, 'sheetData', lnNode)
			DO WHILE !EMPTY(lcSheetData)
				lnRow = 1
				lcRowData = this.GetNodeElement(lcSheetData, 'row', lnRow)
				DO WHILE !EMPTY(lcRowData)
					
				ENDDO
			
				lnNode = lnNode + 1
				lcSheetData = this.GetNodeElement(lcExternalLink, 'sheetData', lnNode)
			ENDDO
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE ReadSharedStringsXML		&& Reads the sharedstrings.xml for opening workbooks
		LPARAMETERS tcFilePath, tnWB
		LOCAL lcStrings, lcString, lnNode, lcRichTextRun, lcXMLText, lnIndex, llFBold, llFItalic, loException, lcT
		LOCAL lnFColor, lcFName, lnFSize, lcULine, llStrkThr, lcFVPos, lnTheme, lnTint, lnIndexed, lcRunPrp
		LOCAL lnR, lcRText, lcRunProp, lcColor, llPrsvSp, llFormatted, llSuccess, lhFile, lnSize
		LOCAL lcStrText, lcCheckSum, loStrings
		
		RAISEEVENT(this, "OnShowStatusMessage", 1, 1)
		TRY
			lcStrings = FILETOSTR(tcFilePath)
			IF this.CodePage = 0
				lcStrings = STRCONV(lcStrings, 11)
			ELSE
				lcStrings = STRCONV(lcStrings, 11, this.CodePage, 1)
			ENDIF
		
			lnNode = 0
			loStrings = this.AddStringToHeap(lcStrings)
			lcString  = this.GetXMLFirstSegment(loStrings, 'si')
			DO WHILE !EMPTY(lcString)
				lnR = 1
				lcRichTextRun = this.GetNodeElement(lcString, 'r', lnR, True)
				IF EMPTY(lcRichTextRun)
					llFormatted = False
					lcT       = this.GetNodeElement(lcString, 't', 1)
					lcXMLText = this.GetNodeElementValue(lcT)
					llPrsvSp  = IIF(EMPTY(this.GetNodeAttributeValue(lcT, "xml:space")), False, True)
		
					IF llPrsvSp
						lcRText = CHRTRAN(lcXMLText, CHR(32), CHR(160))
					ENDIF
				ELSE
					llFormatted = True
					lcXMLText   = ""
					lnIndex     = 0
					DO WHILE !EMPTY(lcRichTextRun)
						lnIndex   = lnIndex + 1
						lcT       = this.GetNodeElement(lcRichTextRun, 't', 1)
						llPrsvSp  = IIF(EMPTY(this.GetNodeAttributeValue(lcT, "xml:space")), False, True)
						lcRText   = this.GetNodeElementValue(lcT)
						lcXMLText = lcXMLText + lcRText
		
						lcRunProp = this.GetNodeElement(lcRichTextRun, 'rPr', 1, True)
						llFBold   = IIF(ATC("<b/>", lcRunProp)>0, True, False)
						llFItalic = IIF(ATC("<i/>", lcRunProp)>0, True, False)
						llStrkthr = IIF(ATC("<strike/>", lcRunProp)>0, True, False)
						IF ATC("<u/>", lcRunProp) > 0
							lcULine = UNDERLINE_SINGLE
						ELSE
							lcULine = this.GetNodeAttributeValue(this.GetNodeElement(lcRunProp, 'u', 1), "val")
							lcULine = IIF(EMPTY(lcULine), UNDERLINE_NONE, lcULine)
						ENDIF
		
						lnFSize   = CAST(this.GetNodeAttributeValue(this.GetNodeElement(lcRunProp, 'sz', 1), "val") AS I)
						lcFName   = this.GetNodeAttributeValue(this.GetNodeElement(lcRunProp, 'rFont', 1), "val")
						lcFVPos   = this.GetNodeAttributeValue(this.GetNodeElement(lcRunProp, 'vertAlign', 1), "val")
		
						lcColor   = this.GetNodeElement(lcRunProp, 'color', 1)
						lnFColor  = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
						lnTheme   = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
						lnTint    = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
						lnIndexed = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
						
						IF llPrsvSp
							lcRText = CHRTRAN(lcRText, CHR(32), CHR(160))
						ENDIF
		
						INSERT INTO xl_strformat (workbook, id, index, stringxml, stringval, fbold, fitalic, fcolor, fname, fsize, uline, strkthr, fvpos, theme, tint, indexed, presvspace) ;
							VALUES (tnWB, lnNode, lnIndex, lcRText, this.GetStringXML(lcRText), llFBold, llFItalic, lnFColor, lcFName, lnFSize, lcULine, llStrkThr, ;
									lcFVPos, lnTheme, lnTint, lnIndexed, llPrsvSp)
		
						lnR = lnR + 1
						lcRichTextRun = this.GetNodeElement(lcString, 'r', lnR, True)
					ENDDO
					llPrsvSp = False
				ENDIF
				lcStrText  = this.GetStringXML(lcXMLText)
				lcCheckSum = this.GetCheckSum(lcStrText)
		
				INSERT INTO xl_strings (id, workbook, checksum, stringxml, stringval, presvspace, formatted) ;
					VALUES (lnNode, tnWB, lcCheckSum, lcXMLText, lcStrText, llPrsvSp, llFormatted)
		
				lnNode = lnNode + 1
				lcString = this.GetXMLNextSegment(loStrings)
			ENDDO
			this.ReleaseStringFromHeap(loStrings)
			this.SetLastId(tnWB, 0, lnNode-1, "xl_strings")
			llSuccess = True
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
		ENDTRY
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE ReadSheetXML		&& Reads the sheet.xml for opening a workbook
		LPARAMETERS tcShFile, tnWB, tnSheet, tcShName, tnShState, tlForceTextFormat, tlReadGraphicData
		LOCAL lcSheet, lcSheetViews, lcSheetView, lcPane, lcMargin, lnXSplit, lnYSplit, lnLeft, lcDimen, lnScale, lnFitToWidth, lnFitToHeight
		LOCAL lnRight, lnTop, lnBottom, lnHeader, lnFooter, lcPageSetup, lnOrientation, lnPaperSize, lnWidth, lnHeight, lcMergeCells
		LOCAL lcMergeCell, lcCellRef, lnNdx, lcSheetData, lnRowLvl, loSheetData, loHyperLinks, loMergeCells, lcPrompt, lcFormula1
		LOCAL lcRowXml, lnRowHt, lnCellRow, lnCellCol, lnCol, loCell, lnTabColNdx, lcTabColRgb, lcTabColor, lcSheetPr, lcErrMsg, lcErrTitle
		LOCAL lcValidations, lnValidType, lcValidation, lnValidStyle, lnOperator, lcDwgFile, lcCommentFile, loTgtRange, loXML, lnTgtSheet
		LOCAL lcFormula2, llAllowBlank, llShowInputMsg, llShowErrMsg, llFormula, llCustHt, lcValidType, lcValidStyle, loRange, lnColLvl
		LOCAL lcValidSqRef, lnIndex, lcHyperLinks, lcHyperLink, lcRef, lcLoc, lcDis, lnRId, lcComment, lcOperator, lcColXml, lcCols
		LOCAL llAutoFilter, llDeleteCol, llDeleteRow, llFormatCell, llFormatCol, llFormatRow, llInsertCol, llInsertRow, llInsertHypr
		LOCAL llObjects, llPivotTbl, llScenarios, llSelLocked, llSelUnlock, llSort, lcProtection, llProtection, lcCol, lnMinCol, lnMaxCol
		LOCAL lcPassword, lcAlgorithm, lnShowGrid, llAddHyperLink, lcTgtFile, lcTgtSheet, lcRelsFileName, lcRelsFile, lcRId, lcDrawing
		LOCAL loException, lcRelationships, loCols, loValidations, llStringsXMLAdded, lnHidden, lnRowHidn, lcAttribute, lcPageSetupPr
		LOCAL lnZoomScale, lnPrnOptHead, lnPrnOptGrid, lnDraft, lnBlackWhite, lnPageScale, lnRow, loSheet, lnFitToPage
		LOCAL lnSummaryBe, lnSummaryRt, lcOutlinePr, lcTopLtCell, lnTabSelctd, lcSelection, lcActivCell, lnPrnOptCenV
		LOCAL lnPrnOptCenH
		
		*-*	Open the sheet for processing
		TRY
			lcSheet = FILETOSTR(tcShFile)
		
		CATCH TO loException
			SET STEP ON
			this.ErrorLevelId = 8
			RAISEEVENT(this, "OnShowErrorMessage", 8, "Invalid XLSX file - missing sheet or invalid sheet: " + tcShFile)
			this.DeleteWorkbook(tnWB)
			THROW
		ENDTRY
		IF EMPTY(lcSheet)
			SET STEP ON
			this.ErrorLevelId = 8
			RAISEEVENT(this, "OnShowErrorMessage", 8, "Invalid XLSX file - missing sheet or invalid sheet: " + tcShFile)
			this.DeleteWorkbook(tnWB)
			THROW
		ENDIF
		IF this.CodePage = 0
			lcSheet = STRCONV(lcSheet, 11)
		ELSE
			lcSheet = STRCONV(lcSheet, 11, this.CodePage, 1)
		ENDIF
		llStringsXMLAdded = False
		
		*-*	Read into memory the sheet relationships file (if exists)
		TRY
			lcRelsFileName  = ADDBS(JUSTPATH(tcShFile)) + "_rels\sheet" + TRANSFORM(tnSheet) + ".xml.rels"
			lcRelsFile      = FILETOSTR(lcRelsFileName)
			lcRelationships = this.GetNodeElement(lcRelsFile, 'Relationships', 1, False)
		
		CATCH TO loException
			lcRelsFile      = ""
			lcRelationships = ""
		ENDTRY
		
		TRY
		*-*	Load the sheet into memory object
			loSheet = this.LoadSheetXml(lcSheet)
		
		*-*	Get the sheetViews settings
			IF EMPTY(loSheet.SheetViews)
				lnShowGrid  = 1
				lnZoomScale = 0
				lnXSplit    = 0
				lnYSplit    = 0
				lcTopLtCell = ""
				lnTabSelctd = 0
				lcActivCell = ""
			ELSE
				lcSheetView = this.GetNodeElement(loSheet.SheetViews, 'sheetView', 1)
				lcAttribute = this.GetNodeAttributeValue(lcSheetView, "showGridLines")
				IF EMPTY(lcAttribute)
					lnShowGrid = 1
				ELSE
					lnShowGrid = CAST(lcAttribute AS I)
				ENDIF
				lcPane = this.GetNodeElement(lcSheetView, 'pane', 1)
				IF EMPTY(lcPane)
					lnXSplit = 0
					lnYSplit = 0
				ELSE
					lnXSplit = CAST(this.GetNodeAttributeValue(lcPane, "xSplit") AS I)
					lnYSplit = CAST(this.GetNodeAttributeValue(lcPane, "ySplit") AS I)
				ENDIF
				lcTopLtCell = this.GetNodeAttributeValue(lcSheetView, "topLeftCell")
				lnZoomScale = CAST(this.GetNodeAttributeValue(lcSheetView, "zoomScale") AS I)
				lnTabSelctd = CAST(this.GetNodeAttributeValue(lcSheetView, "tabSelected") AS I)
		
				lcSelection = this.GetNodeElement(lcSheetView, 'selection', 1)
				lcActivCell = this.GetNodeAttributeValue(lcSelection, "activeCell")
			ENDIF
		
		*-*	Get the sheetPr settings
			IF EMPTY(loSheet.SheetPr)
				lnFitToPage = 0
				lnSummaryBe = 0
				lnSummaryRt = 0
				lnTabColNdx = 0
				lcTabColRgb = ""
			ELSE
				lcPageSetupPr = this.GetNodeElement(loSheet.SheetPr, 'pageSetUpPr', 1)
				lnFitToPage   = CAST(this.GetNodeAttributeValue(lcPageSetupPr, "fitToPage") AS I)
		
				lcOutlinePr = this.GetNodeElement(loSheet.SheetPr, 'outlinePr', 1)
				lnSummaryBe = CAST(this.GetNodeAttributeValue(lcOutlinePr, "summaryBelow") AS I)
				lnSummaryRt = CAST(this.GetNodeAttributeValue(lcOutlinePr, "summaryRight") AS I)
		
				lcTabColor  = this.GetNodeElement(loSheet.SheetPr, 'tabColor', 1)
				lnTabColNdx = CAST(this.GetNodeAttributeValue(lcTabColor, "indexed") AS I)
				lcTabColRgb = this.GetNodeAttributeValue(lcTabColor, "rgb")
			ENDIF
		
		*-*	Get the worksheet margins
			IF EMPTY(loSheet.pageMargins)
				lnLeft   = 0.75
				lnRight  = 0.75
				lnTop    = 0.75
				lnBottom = 0.75
				lnFooter = 0.30
				lnHeader = 0.30
			ELSE
				lnLeft   = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "left") AS N(6,3))
				lnLeft   = IIF(lnLeft = 0.000, 0.75, lnLeft)
				lnRight  = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "right") AS N(6,3))
				lnRight  = IIF(lnRight = 0.000, 0.75, lnRight)
				lnTop    = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "top") AS N(6,3))
				lnTop    = IIF(lnTop = 0.000, 0.75, lnTop)
				lnBottom = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "bottom") AS N(6,3))
				lnBottom = IIF(lnBottom = 0.000, 0.75, lnBottom)
				lnFooter = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "footer") AS N(6,3))
				lnFooter = IIF(lnFooter = 0.000, 0.30, lnFooter)
				lnHeader = CAST(this.GetNodeAttributeValue(loSheet.pageMargins, "header") AS N(6,3))
				lnHeader = IIF(lnHeader = 0.000, 0.30, lnHeader)
			ENDIF
		
		*-*	Get the page setup
			IF EMPTY(loSheet.pageSetup)
				lnOrientation = PORTRAIT_PRINT_ORIENTATION
				lnPaperSize   = PAPERSIZE_LTR
				lnWidth       = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "paperWidth") AS I)
				lnHeight      = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "paperHeight") AS I)
				lcDimen       = RIGHT(this.GetNodeAttributeValue(loSheet.pageSetup, "paperHeight"), 2)
				lnScale       = 100
				lnFitToWidth  = 0
				lnFitToHeight = 0
				lnDraft       = 0
				lnBlackWhite  = 0
				lnPageScale   = 100
			ELSE
				lnOrientation = IIF(this.GetNodeAttributeValue(loSheet.pageSetup, "orientation")='portrait', PORTRAIT_PRINT_ORIENTATION, LANDSCAPE_PRINT_ORIENTATION)
				lnPaperSize   = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "paperSize") AS I)
				lnPaperSize   = IIF(lnPaperSize=0, PAPERSIZE_LTR, lnPaperSize)
				lnWidth       = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "paperWidth") AS I)
				lnHeight      = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "paperHeight") AS I)
				lcDimen       = RIGHT(this.GetNodeAttributeValue(loSheet.pageSetup, "paperHeight"), 2)
				lnScale       = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "scale") AS I)
				lnScale       = IIF(lnScale=0, 100, lnScale)
				lnFitToWidth  = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "fitToWidth") AS I)
				lnFitToHeight = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "fitToHeight") AS I)
				lnDraft       = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "draft") AS I)
				lnBlackWhite  = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "blackAndWhite") AS I)
				lnPageScale   = CAST(this.GetNodeAttributeValue(loSheet.pageSetup, "scale") AS I)
			ENDIF
		
		*-*	Get the sheet protection settings
			IF EMPTY(loSheet.sheetProtection)
				lcPassword   = ""
				lcAlgorithm  = ""
				llProtection = False
				llAutoFilter = True
				llDeleteCol  = True
				llDeleteRow  = True
				llFormatCell = True
				llFormatCol  = True
				llFormatRow  = True
				llInsertCol  = True
				llInsertRow  = True
				llInsertHypr = True
				llObjects    = True
				llPivotTbl   = True
				llScenarios  = True
				llSelLocked  = False
				llSelUnlock  = False
				llSort       = True
			ELSE
				lcPassword   = this.GetNodeAttributeValue(loSheet.sheetProtection, "password")
				lcAlgorithm  = this.GetNodeAttributeValue(loSheet.sheetProtection, "algorithmName")
				llProtection = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "sheet") = "1", True, False)
				llAutoFilter = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "autoFilter") = "1", True, False)
				llDeleteCol  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "deleteColumns") = "1", True, False)
				llDeleteRow  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "deleteRows") = "1", True, False)
				llFormatCell = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "formatCells") = "1", True, False)
				llFormatCol  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "formatColumns") = "1", True, False)
				llFormatRow  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "formatRows") = "1", True, False)
				llInsertCol  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "insertColumns") = "1", True, False)
				llInsertRow  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "insertRows") = "1", True, False)
				llInsertHypr = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "insertHyperlinks") = "1", True, False)
				llObjects    = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "objects") = "1", True, False)
				llPivotTbl   = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "pivotTables") = "1", True, False)
				llScenarios  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "scenarios") = "1", True, False)
				llSelLocked  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "selectLockedCells") = "1", True, False)
				llSelUnlock  = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "selectUnlockedCells") = "1", True, False)
				llSort       = IIF(this.GetNodeAttributeValue(loSheet.sheetProtection, "sort") = "1", True, False)
			ENDIF
		
		*-*	Get the print options
			IF EMPTY(loSheet.printOptions)
				lnPrnOptHead = 0
				lnPrnOptGrid = 0
				lnPrnOptCenV = 0
				lnPrnOptCenH = 0
			ELSE
				lnPrnOptHead = CAST(this.GetNodeAttributeValue(loSheet.printOptions, "headings") AS I)
				lnPrnOptGrid = CAST(this.GetNodeAttributeValue(loSheet.printOptions, "gridLines") AS I)
				lnPrnOptCenV = CAST(this.GetNodeAttributeValue(loSheet.printOptions, "verticalCentered") AS I)
				lnPrnOptCenH = CAST(this.GetNodeAttributeValue(loSheet.printOptions, "horizontalCentered") AS I)
			ENDIF
		
		*-*	Save the worksheet to the cursor
			INSERT INTO xl_sheets (workbook, sheet, shname, state, gridlines, mleft, mright, mtop, mbot, mheader, mfooter, shdeleted, xsplit, ysplit, prnorient, ;
								   papersize, paperwidth, paperheight, paperdimen, scale, fittowidth, fittoheight, tabcolorndx, tabcolorrgb, autofilter, ;
								   deletecol, deleterow, formatcell, formatcol, formatrow, insertcol, insertrow, inserthyper, objects, pivottbl, scenarios, ;
								   sellocked, selunlocked, sort, protection, password, algorithm, zoomscale, prnopthead, prnoptgrid, draft, blackwhite, pagescale, ;
								   fittopage, grpsumbelow, grpsumright, toplfcell, tabselctd, activcell, prnoptctrv, prnoptctrh) ;
				VALUES (tnWB, tnSheet, tcShName, tnShState, lnShowGrid, lnLeft, lnRight, lnTop, lnBottom, lnHeader, lnFooter, False, lnXSplit, lnYSplit, lnOrientation, ;
						lnPaperSize, lnWidth, lnHeight, lcDimen, lnScale, lnFitToWidth, lnFitToHeight, lnTabColNdx, lcTabColRgb, llAutoFilter, llDeleteCol, llDeleteRow, ;
						llFormatCell, llFormatCol, llFormatRow, llInsertCol, llInsertRow, llInsertHypr, llObjects, llPivotTbl, llScenarios, llSelLocked, llSelUnlock, llSort, ;
						llProtection, lcPassword, lcAlgorithm, lnZoomScale, lnPrnOptHead, lnPrnOptGrid, lnDraft, lnBlackWhite, lnPageScale, lnFitToPage, lnSummaryBe, ;
						lnSummaryRt, lcTopLtCell, lnTabSelctd, lcActivCell, lnPrnOptCenV, lnPrnOptCenH)
		
		*-*	Get the sheet cell values and formatting
			IF !EMPTY(loSheet.sheetData)
				loSheetData = this.AddStringToHeap(loSheet.sheetData)
				lcRowXml = this.GetXMLFirstSegment(loSheetData, 'row')
				DO WHILE !EMPTY(lcRowXml)
					lnCellRow = CAST(this.GetNodeAttributeValue(lcRowXml, "r") AS I)
					lnRowHt   = CAST(this.GetNodeAttributeValue(lcRowXml, "ht") AS N(14,7))
					llCustHt  = IIF(CAST(this.GetNodeAttributeValue(lcRowXml, "customHeight") AS I) = 1, True, False)
					lnRowLvl  = CAST(this.GetNodeAttributeValue(lcRowXml, "outlineLevel") AS I)
					lnRowHidn = CAST(this.GetNodeAttributeValue(lcRowXml, "hidden") AS I)
					IF !llCustHt .OR. !(lnRowHt > 0.00 .AND. lnRowHt != 30)
						lnRowHt = -1
					ENDIF
		
					lnCol     = 1
					lnCellCol = 1
					lcColXml  = this.GetNodeElement(lcRowXml, 'c', lnCol)
					DO WHILE !EMPTY(lcColXml)                                    && Process each column in a row
						lcCellRef = this.GetNodeAttributeValue(lcColXml, "r")
						lnCellCol = this.ColumnAsciiToIndex(lcCellRef)
						loCell    = this.ReadCellValueFormat(tnWB, tnSheet, lcColXml, lnCellRow, lnCellCol)
						IF tlForceTextFormat .AND. !INLIST(loCell.DataType, DATA_TYPE_CHAR, DATA_TYPE_FORMULA)
							loCell.DataType  = DATA_TYPE_CHAR
							loCell.StringNdx = this.AddStringValue(tnWB, TRANSFORM(loCell.CellVal), False)
							loCell.CellVal   = "StringValue"
						ENDIF
						TRY
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, cellvalue, datatype, formulatype, formulasi, stringid, cellxfs, celldeleted) ;
								VALUES (tnWB, tnSheet, lnCellRow, lnCellCol, loCell.CellVal, loCell.DataType, loCell.FormulaType, loCell.FormulaSi, ;
										loCell.StringNdx, loCell.CellXfsId, False)
						CATCH TO loException
							SET STEP ON
						ENDTRY
						IF loCell.CellVal = "StringValue"
							llStringsXMLAdded = True
						ENDIF
						lnCol = lnCol + 1
						lcColXml = this.GetNodeElement(lcRowXml, 'c', lnCol)
					ENDDO
					INSERT INTO xl_rows (workbook, sheet, row, maxcol, height, rowlevel, collapsed, HIDDEN) ;
						VALUES (tnWB, tnSheet, lnCellRow, lnCellCol, lnRowHt, lnRowLvl, False, lnRowHidn)
		
					lcRowXml = this.GetXMLNextSegment(loSheetData)
				ENDDO
				this.ReleaseStringFromHeap(loSheetData)
			ENDIF
		
		*-*	Get the defined column widths
			IF !EMPTY(loSheet.Cols)
				loCols = this.AddStringToHeap(loSheet.Cols)
				lcCol  = this.GetXMLFirstSegment(loCols, 'col')
				DO WHILE !EMPTY(lcCol)
					lnMinCol = CAST(this.GetNodeAttributeValue(lcCol, "min") AS I)
					lnMaxCol = CAST(this.GetNodeAttributeValue(lcCol, "max") AS I)
					lnWidth  = CAST(this.GetNodeAttributeValue(lcCol, "width") AS N(16,9)) - 0.71093750
					lnHidden = CAST(this.GetNodeAttributeValue(lcCol, "hidden") AS I)
					lnColLvl = CAST(this.GetNodeAttributeValue(lcCol, "outlineLevel") AS I)
					FOR lnCol=lnMinCol TO lnMaxCol
						INSERT INTO xl_columns (workbook, sheet, col, width, collevel, collapsed, hidden) VALUES (tnWB, tnSheet, lnCol, lnWidth, lnColLvl, False, lnHidden)
					ENDFOR
					lcCol = this.GetXMLNextSegment(loCols)
				ENDDO
				this.ReleaseStringFromHeap(loCols)
			ENDIF
		
		*-*	Get the defined merged cells
			IF !EMPTY(loSheet.MergeCells)
				loMergeCells = this.AddStringToHeap(loSheet.MergeCells)
				lcMergeCell  = this.GetXMLFirstSegment(loMergeCells, 'mergeCell')
				DO WHILE !EMPTY(lcMergeCell)
					lcCellRef = this.GetNodeAttributeValue(lcMergeCell, "ref")
					loRange   = this.ConvertRangeToColumnRowValues(lcCellRef)
					loRange.EndRow = IIF(loRange.EndRow>0, loRange.EndRow, loRange.BegRow)
					loRange.EndCol = IIF(loRange.EndCol>0, loRange.EndCol, loRange.BegCol)
					IF loRange.BegCol > 0 .AND. loRange.EndCol > 0
						INSERT INTO xl_mergecells (workbook, sheet, begrow, begcol, endrow, endcol) ;
							VALUES (tnWB, tnSheet, loRange.BegRow, loRange.BegCol, loRange.EndRow, loRange.EndCol)
					ENDIF
					lcMergeCell = this.GetXMLNextSegment(loMergeCells)
				ENDDO
				this.ReleaseStringFromHeap(loMergeCells)
			ENDIF
		
		*-*	Get sheet hyperlink data
			IF !EMPTY(loSheet.HyperLinks)
				loHyperLinks = this.AddStringToHeap(loSheet.HyperLinks)
				lcHyperLink  = this.GetXMLFirstSegment(loHyperLinks, 'hyperlink')
				DO WHILE !EMPTY(lcHyperLink)
					lcTgtSheet = ""
					llAddHyperLink = True
					lcRef = this.GetNodeAttributeValue(lcHyperLink, "ref")
					loRange = this.ConvertRangeToColumnRowValues(lcRef)
					loRange.EndRow = IIF(loRange.EndRow>0, loRange.EndRow, loRange.BegRow)
					loRange.EndCol = IIF(loRange.EndCol>0, loRange.EndCol, loRange.BegCol)
				
					lcLoc = this.GetNodeAttributeValue(lcHyperLink, "location")
					IF EMPTY(lcLoc)
						lcRId = this.GetNodeAttributeValue(lcHyperLink, "r:id")
						lnRId = CAST(SUBSTR(lcRId, 4) AS I)
				
						IF EMPTY(lcRelsFile) .OR. EMPTY(lcRId)
							llAddHyperLink = False
						ELSE
							lcTgtFile = this.GetTargetFromRelationships(lcRId, "", lcRelationships)
							IF ISNULL(lcTgtFile)
								llAddHyperLink = False
							ENDIF
						ENDIF
						IF llAddHyperLink
							INSERT INTO xl_hyperlinks (workbook, sheet, begrow, begcol, endrow, endcol, targettype, relid) ;
								VALUES (tnWB, tnSheet, loRange.BegRow, loRange.BegCol, loRange.EndRow, loRange.EndCol, HYPERLINK_TYPE_FILE, lnRId)
				
							INSERT INTO xl_relationships (workbook, sheet, relsrc, relid, reltype, target, EXTERNAL) ;
								VALUES (tnWB, tnSheet, RELS_SRC_SHEET, lnRId, RELS_TYPE_HYPERLINK, lcTgtFile, True)
						ENDIF
					ELSE
						lcDis = this.GetNodeAttributeValue(lcHyperLink, "display")
				
						lnNdx = ATC("!", lcLoc)
						IF lnNdx = 0
							lnTgtSheet = tnSheet
						ELSE
							lcTgtSheet = ALLTRIM(LEFT(lcLoc, lnNdx-1), 1, "'")
							lnTgtSheet = this.GetSheetIndex(tnWB, lcTgtSheet)
						ENDIF
						loTgtRange = this.ConvertRangeToColumnRowValues(SUBSTR(lcLoc, lnNdx+1))
				
						INSERT INTO xl_hyperlinks (workbook, sheet, begrow, begcol, endrow, endcol, targettype, tgtsheet, tgtsheetname, tgtbegrow, tgtbegcol, tgtendrow, tgtendcol, DISPLAY, relid) ;
							VALUES (tnWB, tnSheet, loRange.BegRow, loRange.BegCol, loRange.EndRow, loRange.EndCol, HYPERLINK_TYPE_SHEET, lnTgtSheet, lcTgtSheet, ;
									loTgtRange.BegRow, loTgtRange.BegCol, loTgtRange.EndRow, loTgtRange.EndCol, lcDis, 0)
					ENDIF
					lcHyperLink = this.GetXMLNextSegment(loHyperLinks)
				ENDDO
				this.ReleaseStringFromHeap(loHyperLinks)
			ENDIF
		
		*-*	Get the validations
			IF !EMPTY(loSheet.dataValidations)
				loValidations = this.AddStringToHeap(loSheet.dataValidations)
				lcValidation  = this.GetXMLFirstSegment(loValidations, 'dataValidation')
				DO WHILE !EMPTY(lcValidation)
					lcValidSqRef = this.GetNodeAttributeValue(lcValidation, "sqref")
					lcValidType  = this.GetNodeAttributeValue(lcValidation, "type")
					DO CASE
						CASE lcValidType = "none"
							lnValidType = NONE_VALID_TYPE
			
						CASE lcValidType = "whole"
							lnValidType = WHOLE_VALID_TYPE
			
						CASE lcValidType = "decimal"
							lnValidType = DECIMAL_VALID_TYPE
			
						CASE lcValidType = "list"
							lnValidType = LIST_VALID_TYPE
			
						CASE lcValidType = "date"
							lnValidType = DATE_VALID_TYPE
			
						CASE lcValidType = "time"
							lnValidType = TIME_VALID_TYPE
			
						CASE lcValidType = "textLength"
							lnValidType = TXTLEN_VALID_TYPE
			
						CASE lcValidType = "custom"
							lnValidType = CUSTOM_VALID_TYPE
			
						OTHERWISE
							lnValidType = NONE_VALID_TYPE
					ENDCASE
			
					lcValidStyle = this.GetNodeAttributeValue(lcValidation, "errorStyle")
					DO CASE
						CASE lcValidStyle = "stop"
							lnValidStyle = STOP_VALID_STYLE
			
						CASE lcValidStyle = "warning"
							lnValidStyle = WARN_VALID_STYLE
			
						CASE lcValidStyle = "information"
							lnValidStyle = INFO_VALID_STYLE
			
						OTHERWISE
							lnValidStyle = 0
					ENDCASE
			
					lcOperator = this.GetNodeAttributeValue(lcValidation, "operator")
					DO CASE
						CASE lcOperator = "between"
							lnOperator = BETWEEN_VALID_OPER
			
						CASE lcOperator = "notBetween"
							lnOperator = NOTBETW_VALID_OPER
			
						CASE lcOperator = "equal"
							lnOperator = EQUAL_VALID_OPER
			
						CASE lcOperator = "notEqual"
							lnOperator = NOTEQUAL_VALID_OPER
			
						CASE lcOperator = "lessThan"
							lnOperator = LESSTHAN_VALID_OPER
			
						CASE lcOperator = "lessThanOrEqual"
							lnOperator = LESSOREQUAL_VALID_OPER
			
						CASE lcOperator = "greaterThan"
							lnOperator = GREATTHAN_VALID_OPER
			
						CASE lcOperator = "greaterThanOrEqual"
							lnOperator = GREATOREQUAL_VALID_OPER
			
						OTHERWISE
							lnOperator = 0
					ENDCASE
			
					lcErrMsg       = this.GetNodeAttributeValue(lcValidation, "error")
					lcErrTitle     = this.GetNodeAttributeValue(lcValidation, "errorTitle")
					lcPrompt       = this.GetNodeAttributeValue(lcValidation, "prompt")
					llAllowBlank   = IIF(CAST(this.GetNodeAttributeValue(lcValidation, "allowBlank") AS I)=1, True, False)
					llShowInputMsg = IIF(CAST(this.GetNodeAttributeValue(lcValidation, "showInputMessage") AS I)=1, True, False)
					llShowErrMsg   = IIF(CAST(this.GetNodeAttributeValue(lcValidation, "showErrorMessage") AS I)=1, True, False)
			
					lcFormula1     = this.GetNodeElementValue(this.GetNodeElement(lcValidation, 'formula1', 1, True))
					lcFormula2     = this.GetNodeElementValue(this.GetNodeElement(lcValidation, 'formula2', 1, True))
			
					IF LEFT(lcFormula1, 1) = '"' .OR. LEFT(lcFormula2, 1) = '"'
						lcFormula1 = ALLTRIM(lcFormula1, 1, '"')
						lcFormula2 = ALLTRIM(lcFormula2, 1, '"')
						llFormula  = False
					ELSE
						llFormula = True
					ENDIF
					INSERT INTO xl_validation (workbook, sheet, vtype, vstyle, voperator, errmsg, errtitle, vprompt, allowblank, showinpmsg, showerrmsg, formula, formula1, formula2) ;
						VALUES (tnWB, tnSheet, lnValidType, lnValidStyle, lnOperator, lcErrMsg, lcErrTitle, lcPrompt, llAllowBlank, llShowInputMsg, llShowErrMsg, llFormula, lcFormula1, lcFormula2)
					lnIndex = xl_validation.validndx
			
					loCell = this.CellRefAsciiToIndex(lcValidSqRef)
					IF this.GetCellRecord(tnWB, tnSheet, loCell.Row, loCell.Column)
						REPLACE xl_cells.validndx WITH lnIndex IN xl_cells
					ELSE
						INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
							VALUES (tnWB, tnSheet, loCell.Row, loCell.Column, DATA_TYPE_NONE, 0, False, -1, lnIndex)
			
						this.SetRowMaxColumn(tnWB, tnSheet, loCell.Row, loCell.Column)
					ENDIF
					lcValidation = this.GetXMLNextSegment(loValidations)
				ENDDO
				this.ReleaseStringFromHeap(loValidations)
			ENDIF
		
		*-*	Get the graphic data which includes comments, images, and shapes
			IF tlReadGraphicData
		*-* 	Get any comments added to sheet
				IF !EMPTY(loSheet.legacyDrawing) .AND. !EMPTY(lcRelationships)
					lcRId = this.GetNodeAttributeValue(loSheet.legacyDrawing, "r:id")
					lcCommentFile = this.GetTargetFromRelationships(lcRId, JUSTPATH(tcShFile), lcRelationships)
					IF !ISNULL(lcCommentFile)
						TRY
							this.ReadCommentXML(tnWB, tnSheet, lcCommentFile)
		
						CATCH TO loException
							SET STEP ON
						ENDTRY
					ENDIF
				ENDIF
		
		*-* 	Get any images or shapes added to sheet
				IF !EMPTY(loSheet.drawing) .AND. !EMPTY(lcRelationships)
					lcRId = this.GetNodeAttributeValue(loSheet.drawing, "r:id")
					lcDwgFile = this.GetTargetFromRelationships(lcRId, JUSTPATH(tcShFile), lcRelationships)
					IF !ISNULL(lcDwgFile)
						TRY
							this.ReadDrawingXML(tnWB, tnSheet, lcDwgFile, CAST(SUBSTR(lcRId, 4) AS I))
		
						CATCH TO loException
							SET STEP ON
						ENDTRY
					ENDIF
				ENDIF
			ENDIF
		
		CATCH TO loException
			SET STEP ON
			this.ErrorLevelId = 8
			RAISEEVENT(this, "OnShowErrorMessage", 8, "Sheet " + tcShFile + ":" + loException.Message)
			this.DeleteWorkbook(tnWB)
			THROW
		ENDTRY
		RETURN llStringsXMLAdded
	ENDPROC

	PROTECTED PROCEDURE ReadStylesXML		&& Reads the styles.xml file for opening a workbook
		LPARAMETERS tcFilePath, tnWB
		LOCAL lcCellXfs, lcXf, lnIndent, lnWrapTx, lcAlign, lcStyleSheet, lnNode, lnNumFmtId, lnFontId, lnFillId, lnBorderId
		LOCAL lcHAlign, lcVAlign, lcNumFmt, lcFormatCode, lcFont, lnIndexed, lnFSize, lcFName, llFBold, llFItalic, llStrkthr
		LOCAL lcFVPos, lcULine, lnFColor, lnTheme, lnTint, lcColor, lcFill, lcPatternFill, lcPatternType, lnFgColor, lnFgIndexed, lcBorder
		LOCAL lnBgColor, lnBgIndexed, lcBLeft, lcLStyle, lnLColor, lnLTheme, lnLTint, lnLIndex, lcBRight, lcRStyle, lnRColor, lnRTheme
		LOCAL lnRTint, lnRIndex, lcBTop, lcTStyle, lnTColor, lnTTheme, lnTTint, lnTIndex, lcBBottm, lcBStyle, lnBColor, lnBTheme, lnBTint, lnBIndex
		LOCAL lnDTint, lnDIndex, lcBDiag, lcDStyle, lnDColor, lnDTheme, lcIndexedColors, lcRgbColor, lcMruColors, lnIdCnt, loStyles
		LOCAL loCellXfs, loNumFmts, loFonts, loFills, loIndexedColors, loMruColors, lnLastId, lnRotation, loBorders
		LOCAL lnDiagDn, lnDiagUp, loColors, lcHexColor, lcMruColor, lnApplyFont, lnApplyBorder, lnApplyAlign
		LOCAL lnApplyProt, lnFgTheme, lnBgTheme
		
		RAISEEVENT(this, "OnShowStatusMessage", 1, 2)
		
		*-*	Open the styles sheet for processing
		lcStyleSheet = FILETOSTR(tcFilePath)
		IF this.CodePage = 0
			lcStyleSheet = STRCONV(lcStyleSheet, 11)
		ELSE
			lcStyleSheet = STRCONV(lcStyleSheet, 11, this.CodePage, 1)
		ENDIF
		
		*-*	Load the styles.xml into memory
		loStyles = this.LoadStylesXml(lcStyleSheet)
		
		*-*	Retreive the cell format mapping information
		lnIdCnt = -1
		IF !EMPTY(loStyles.cellXfs)
			loCellXfs = this.AddStringToHeap(loStyles.cellXfs)
			lcXf      = this.GetXMLFirstSegment(loCellXfs, 'xf')
			DO WHILE !EMPTY(lcXf)
				lnIdCnt    = lnIdCnt + 1
				lcAlign    = this.GetNodeElement(lcXf, 'alignment', 1)
				lcHAlign   = this.GetNodeAttributeValue(lcAlign, "horizontal")
				lcVAlign   = this.GetNodeAttributeValue(lcAlign, "vertical")
				lnNumFmtId = CAST(this.GetNodeAttributeValue(lcXf, "numFmtId") AS I)
				lnFontId   = CAST(this.GetNodeAttributeValue(lcXf, "fontId") AS I)
				lnFillId   = CAST(this.GetNodeAttributeValue(lcXf, "fillId") AS I)
				lnBorderId = CAST(this.GetNodeAttributeValue(lcXf, "borderId") AS I)
				lnIndent   = CAST(this.GetNodeAttributeValue(lcAlign, "indent") AS I)
				lnWrapTx   = CAST(this.GetNodeAttributeValue(lcAlign, "wrapText") AS I)
				lnRotation = CAST(this.GetNodeAttributeValue(lcAlign, "textRotation") AS I)
			
				lnApplyFont   = CAST(this.GetNodeAttributeValue(lcXf, "applyFont") AS I)
				lnApplyBorder = CAST(this.GetNodeAttributeValue(lcXf, "applyFont") AS I)
				lnApplyAlign  = CAST(this.GetNodeAttributeValue(lcXf, "applyFont") AS I)
				lnApplyProt   = CAST(this.GetNodeAttributeValue(lcXf, "applyFont") AS I)
			
				INSERT INTO xl_cellxfs (workbook, id, numFmtId, fontId, fillId, borderId, halign, valign, indent, wraptext, rotation, celllocked, cellhidden, ;
										applyfont, applyborder, applyalign, applyprot) ;
					VALUES (tnWB, lnIdCnt, lnNumFmtId, lnFontId, lnFillId, lnBorderId, lcHAlign, lcVAlign, lnIndent, lnWrapTx, lnRotation, 1, 0, ;
							lnApplyFont, lnApplyBorder, lnApplyAlign, lnApplyProt)
			
				lcXf = this.GetXMLNextSegment(loCellXfs)
			ENDDO
			this.ReleaseStringFromHeap(loCellXfs)
		ENDIF
		this.SetLastId(tnWB, 0, lnIdCnt, "xl_cellxfs")
		
		*-*	Retreive the number format information
		lnLastId = START_NUMERIC_FORMAT_ID
		IF !EMPTY(loStyles.cellXfs)
			loNumFmts = this.AddStringToHeap(loStyles.cellXfs)
			lcNumFmt  = this.GetXMLFirstSegment(loNumFmts, 'numFmt')
			DO WHILE !EMPTY(lcNumFmt)
				lnNumFmtId   = CAST(this.GetNodeAttributeValue(lcNumFmt, "numFmtId") AS I)
				lcFormatCode = this.GetNodeAttributeValue(lcNumFmt, "formatCode")
				lnLastId     = MAX(lnNumFmtId, lnLastId)
			
				INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
					VALUES (tnWB, lnNumFmtId, lcFormatCode, this.GetStringXML(lcFormatCode), False)
			
				lcNumFmt = this.GetXMLNextSegment(loNumFmts)
			ENDDO
			this.ReleaseStringFromHeap(loNumFmts)
		ENDIF
		this.SetLastId(tnWB, 0, lnLastId, "xl_numfmts")
		
		*-*	Retreive the font informaton
		lnIdCnt = -1
		IF !EMPTY(loStyles.fonts)
			loFonts = this.AddStringToHeap(loStyles.fonts)
			lcFont  = this.GetXMLFirstSegment(loFonts, 'font')
			DO WHILE !EMPTY(lcFont)
				lnIdCnt   = lnIdCnt + 1
				lnFSize   = CAST(this.GetNodeAttributeValue(this.GetNodeElement(lcFont, 'sz', 1), "val") AS I)
				lnFSize   = IIF(lnFSize>0, lnFSize, 10)
				lcFName   = this.GetNodeAttributeValue(this.GetNodeElement(lcFont, 'name', 1), "val")
				llFBold   = IIF(ATC("<b/>", lcFont)>0, True, False)
				llFItalic = IIF(ATC("<i/>", lcFont)>0, True, False)
				llStrkthr = IIF(ATC("<strike/>", lcFont)>0, True, False)
				lcFVPos   = this.GetNodeAttributeValue(this.GetNodeElement(lcFont, 'vertAlign', 1), "val")
			
				IF ATC("<u/>", lcFont) > 0
					lcULine = UNDERLINE_SINGLE
				ELSE
					lcULine = this.GetNodeAttributeValue(this.GetNodeElement(lcFont, 'u', 1), "val")
					lcULine = IIF(EMPTY(lcULine), UNDERLINE_NONE, lcULine)
				ENDIF
			
				lcColor   = this.GetNodeElement(lcFont, 'color', 1)
				lnFColor  = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
				lnTheme   = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
				lnTint    = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
				lnIndexed = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
			
				INSERT INTO xl_fonts (workbook, id, fname, fsize, fcolor, fbold, fitalic, uline, strkthr, fvpos, theme, tint, indexed) ;
					VALUES (tnWB, lnIdCnt, lcFName, lnFSize, lnFColor, llFBold, llFItalic, lcULine, llStrkthr, lcFVPos, lnTheme, lnTint, lnIndexed)
					
				lcFont = this.GetXMLNextSegment(loFonts)
			ENDDO
			this.ReleaseStringFromHeap(loFonts)
		ENDIF
		this.SetLastId(tnWB, 0, lnIdCnt, "xl_fonts")
		
		*-*	Retreive the cell fill information
		lnIdCnt = -1
		IF !EMPTY(loStyles.fills)
			loFills = this.AddStringToHeap(loStyles.fills)
			lcFill  = this.GetXMLFirstSegment(loFills, 'fill')
			DO WHILE !EMPTY(lcFill)
				lnIdCnt       = lnIdCnt + 1
				lcPatternFill = this.GetNodeElement(lcFill, 'patternFill', 1)
				lcPatternType = this.GetNodeAttributeValue(lcPatternFill, "patternType")
				lcPatternType = IIF(EMPTY(lcPatternType), "none", lcPatternType)
				lnTheme       = CAST(this.GetNodeAttributeValue(lcPatternFill, "theme") AS I)
			
				lcColor = this.GetNodeElement(lcPatternFill, 'fgColor', 1)
				IF EMPTY(lcColor)
					lnFgColor   = 0
					lnFgIndexed = 0
					lnFgTheme   = -1
				ELSE
					lnFgColor   = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnFgIndexed = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
					lnFgTheme   = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
				ENDIF
				lcColor = this.GetNodeElement(lcPatternFill, 'bgColor', 1)
				IF EMPTY(lcColor)
					lnBgColor   = 0
					lnBgIndexed = 0
					lnBgTheme   = -1
				ELSE
					lnBgColor   = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnBgIndexed = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
					lnBgTheme   = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
				ENDIF
				INSERT INTO xl_fills (workbook, id, patttype, theme, tint, fgcolor, bgcolor, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, lnIdCnt, lcPatternType, lnTheme, lnTint, lnFgColor, lnBgColor, lnFgIndexed, lnBgIndexed, lnFgTheme, lnBgTheme)
			
				lcFill = this.GetXMLNextSegment(loFills)
			ENDDO
			this.ReleaseStringFromHeap(loFills)
		ENDIF
		this.SetLastId(tnWB, 0, lnIdCnt, "xl_fills")
		
		*-*	Retreive the cell border information
		lnIdCnt = -1
		IF !EMPTY(loStyles.borders)
			loBorders = this.AddStringToHeap(loStyles.borders)
			lcBorder  = this.GetXMLFirstSegment(loBorders, 'border')
			DO WHILE !EMPTY(lcBorder)
				lnIdCnt  = lnIdCnt + 1
				lcBLeft  = this.GetNodeElement(lcBorder, 'left', 1)
				lcLStyle = this.GetNodeAttributeValue(lcBLeft, "style")
				lcColor  = this.GetNodeElement(lcBLeft, 'color', 1)
				IF EMPTY(lcColor)
					lnLColor = RGB(0, 0, 0)
					lnLTheme = 0
					lnLTint  = 0
					lnLIndex = 0
				ELSE
					lnLColor = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnLTheme = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
					lnLTint  = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
					lnLIndex = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
				ENDIF
			
				lcBRight = this.GetNodeElement(lcBorder, 'right', 1)
				lcRStyle = this.GetNodeAttributeValue(lcBRight, "style")
				lcColor  = this.GetNodeElement(lcBRight, 'color', 1)
				IF EMPTY(lcColor)
					lnRColor = RGB(0, 0, 0)
					lnRTheme = 0
					lnRTint  = 0
					lnRIndex = 0
				ELSE
					lnRColor = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnRTheme = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
					lnRTint  = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
					lnRIndex = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
				ENDIF
			
				lcBTop   = this.GetNodeElement(lcBorder, 'top', 1)
				lcTStyle = this.GetNodeAttributeValue(lcBTop, "style")
				lcColor  = this.GetNodeElement(lcBTop, 'color', 1)
				IF EMPTY(lcColor)
					lnTColor = RGB(0, 0, 0)
					lnTTheme = 0
					lnTTint  = 0
					lnTIndex = 0
				ELSE
					lnTColor = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnTTheme = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
					lnTTint  = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
					lnTIndex = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
				ENDIF
			
				lcBBottm = this.GetNodeElement(lcBorder, 'bottom', 1)
				lcBStyle = this.GetNodeAttributeValue(lcBBottm, "style")
				lcColor  = this.GetNodeElement(lcBBottm, 'color', 1)
				IF EMPTY(lcColor)
					lnBColor = RGB(0, 0, 0)
					lnBTheme = 0
					lnBTint  = 0
					lnBIndex = 0
				ELSE
					lnBColor = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnBTheme = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
					lnBTint  = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
					lnBIndex = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
				ENDIF
			
				lcBDiag  = this.GetNodeElement(lcBorder, 'diagonal', 1)
				lcDStyle = this.GetNodeAttributeValue(lcBDiag, "style")
				lnDiagDn = CAST(this.GetNodeAttributeValue(lcBDiag, "diagonalDown") AS I)
				lnDiagUp = CAST(this.GetNodeAttributeValue(lcBDiag, "diagonalUp") AS I)
				lcColor  = CAST(this.GetNodeElement(lcBDiag, 'color', 1) AS I)
				IF EMPTY(lcColor)
					lnDColor = RGB(0, 0, 0)
					lnDTheme = 0
					lnDTint  = 0
					lnDIndex = 0
				ELSE
					lnDColor = this.ConvertHexStringToNumeric(this.GetNodeAttributeValue(lcColor, "rgb"))
					lnDTheme = CAST(this.GetNodeAttributeValue(lcColor, "theme") AS I)
					lnDTint  = CAST(this.GetNodeAttributeValue(lcColor, "tint") AS I)
					lnDIndex = CAST(this.GetNodeAttributeValue(lcColor, "indexed") AS I)
				ENDIF
			
				INSERT INTO xl_borders (workbook, id, lstyle, lcolor, ltheme, ltint, lindexed, rstyle, rcolor, rtheme, rtint, rindexed, tstyle, tcolor, ttheme, ttint, tindexed, ;
									    bstyle, bcolor, btheme, btint, bindexed, dstyle, dcolor, dtheme, dtint, dindexed, diagdn, diagup) ;
					VALUES (tnWB, lnIdCnt, lcLStyle, lnLColor, lnLTheme, lnLTint, lnLIndex, lcRStyle, lnRColor, lnRTheme, lnRTint, lnRIndex, lcTStyle, lnTColor, lnTTheme, ;
							lnTTint, lnTIndex, lcBStyle, lnBColor, lnBTheme, lnBTint, lnBIndex, lcDStyle, lnDColor, lnDTheme, lnDTint, lnDIndex, lnDiagDn, lnDiagUp)
			
				lcBorder = this.GetXMLNextSegment(loBorders)
			ENDDO
			this.ReleaseStringFromHeap(loBorders)
		ENDIF
		this.SetLastId(tnWB, 0, lnIdCnt, "xl_borders")
		
		*-*	Retreive the color information
		IF !EMPTY(loStyles.borders)
			loColors = this.AddStringToHeap(loStyles.borders)
		
			lcIndexedColors = this.GetXMLSegment(loColors, 'indexedColors')
			IF !EMPTY(lcIndexedColors)
				lnIdCnt = -1
				lnNode  = 1
				loIndexedColors = this.AddStringToHeap(lcIndexedColors)
				lcRgbColor = this.GetXMLFirstSegment(loIndexedColors, 'rgbColor')
				DO WHILE !EMPTY(lcRgbColor)
					lnIdCnt    = lnIdCnt + 1
					lcHexColor = this.GetNodeAttributeValue(lcRgbColor, "rgb")
		
					INSERT INTO xl_ndxcolors (workbook, indexid, rgbcolor) VALUES (tnWB, lnNode, lcHexColor)
		
					lnNode = lnNode + 1
					lcRgbColor = this.GetXMLNextSegment(loIndexedColors)
				ENDDO
				this.ReleaseStringFromHeap(loIndexedColors)
				this.SetLastId(tnWB, 0, lnIdCnt, "xl_ndxcolors")
			ENDIF
		
			lcMruColors = this.GetXMLSegment(loColors, 'mruColors')
			IF !EMPTY(lcMruColors)
				lnIdCnt = -1
				lnNode  = 1
				loMruColors = this.AddStringToHeap(lcMruColors)
				lcMruColor  = this.GetXMLFirstSegment(loMruColors, 'color')
				DO WHILE !EMPTY(lcMruColor)
					lnIdCnt    = lnIdCnt + 1
					lcHexColor = this.GetNodeAttributeValue(lcMruColor, "rgb")
		
					INSERT INTO xl_mrucolors (workbook, indexid, rgbcolor) VALUES (tnWB, lnNode, lcHexColor)
		
					lnNode = lnNode + 1
					lcMruColor = this.GetXMLNextSegment(loMruColors)
				ENDDO
				this.ReleaseStringFromHeap(loMruColors)
				this.SetLastId(tnWB, 0, lnIdCnt, "xl_mrucolors")
			ENDIF
			this.ReleaseStringFromHeap(loColors)
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE ReleaseStringFromHeap		&& Releases the string from the heap memory
		LPARAMETERS toString
		apiHeapFree(apiGetProcessHeap(), 0, toString.BaseAdr)
		RETURN .NULL.
	ENDPROC

	PROTECTED PROCEDURE RemoveWorkingDirectories		&& Removes the working directories
		LPARAMETERS tcDir
		LOCAL llReturn, loException
		TRY
			llReturn = IIF(apiRemoveDirectory(tcDir) != 0, True, False)
		
		CATCH TO loException
			SET STEP ON
			llReturn = False
		ENDTRY
		RETURN llReturn
	ENDPROC

	PROCEDURE RenameSheet		&& Renames the selected sheet
		LPARAMETERS tnWB, txSheet, tcSheetName
		LOCAL llReturn
		llReturn = False
		DO CASE
			CASE VARTYPE(txSheet) = "C"
				IF SEEK(BINTOC(tnWB)+UPPER(txSheet), "xl_sheets", "shname")
					REPLACE xl_sheets.shname WITH tcSheetName IN xl_sheets
					llReturn = True
				ENDIF
		
			CASE VARTYPE(txSheet) = "N"
				IF SEEK(BINTOC(tnWB)+BINTOC(txSheet), "xl_sheets", "sheet")
					REPLACE xl_sheets.shname WITH tcSheetName IN xl_sheets
					llReturn = True
				ENDIF
		ENDCASE
		RETURN llReturn
	ENDPROC

	PROCEDURE ResetColumnWidth		&& Resets the column width to default
		LPARAMETERS tnWB, tnSheet, tnColumn
		IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
			DELETE IN xl_columns
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE RestoreVFPSettings		&& Restores the VFP system settings
		LOCAL lxSetting
		IF this.VFPSettings.ValuesSet
			lxSetting = this.VFPSettings.Exact
			SET EXACT &lxSetting
		
			lxSetting = this.VFPSettings.Safety
			SET SAFETY &lxSetting
		
			lxSetting = this.VFPSettings.SetDeleted
			SET DELETED &lxSetting
		
			IF !EMPTY(this.VFPSettings.Alias)
				lxSetting = this.VFPSettings.Alias
				SELECT &lxSetting
			ENDIF
		
			lxSetting = this.VFPSettings.SetPoint
			IF lxSetting != "."
				SET POINT TO (lxSetting)
				this.VFPSettings.SetPoint = ""
			ENDIF
		
			lxSetting = this.VFPSettings.Separator
			IF lxSetting != ","
				SET SEPARATOR TO (lxSetting)
				this.VFPSettings.Separator = ""
			ENDIF
		
			this.VFPSettings.ValuesSet  = False
			this.VFPSettings.Alias      = ""
			this.VFPSettings.Currency   = ""
			this.VFPSettings.Exact      = ""
			this.VFPSettings.Safety     = ""
			this.VFPSettings.SetDeleted = ""
		ENDIF
	ENDPROC

	PROTECTED PROCEDURE SaveAsUTF8		&& Saves the file as a UTF-8
		LPARAMETERS tcFileName
		LOCAL loException
		TRY
			STRTOFILE(this.ConvertFileToString(tcFileName, 9), tcFileName, 4)
		CATCH TO loException
		ENDTRY
	ENDPROC

	PROCEDURE SaveGridToWorkbook		&& Saves the selected grid to a workbook
		LPARAMETERS toGrid, txWB, tlFreeze, tlSaveWB, tcSheetName, tlInclHiddenCols, tlShowGridLines, tnSheet, tnBegRow, tnBegCol
		LOCAL lcGridAlias, lnCol, lnWB, lnSh, lnRow, lcField, loReturn, loColumn, lnStyle, lnColCount, lnDefStyle
		LOCAL loFontInfo, lcDataType, lcCol, lnCellStyle, loGrid, lnFldCnt, lnCustId
		LOCAL lnXfsId, lxCellValue, lcFontName, lnFontSize, llFontBold, llFontItalic, loException
		LOCAL lnCellXfsId, lnNumFmtsId, lnFontColor, lcFontUnderLine, llFontStrikeThru, lnFillColor
		LOCAL ARRAY laRowCount[1], laColOrder[1], laTblFields[1]
		loReturn = CREATEOBJECT("Empty")
		ADDPROPERTY(loReturn, "Workbook",  0)
		ADDPROPERTY(loReturn, "Sheet", 0)
		IF VARTYPE(toGrid) != "O" .AND. PEMSTATUS(toGrid, "BaseClass", 5) .AND. LOWER(toGrid.BaseClass) != "grid"
			RETURN loReturn
		ENDIF
		DO CASE
			CASE PCOUNT() = 0
				RETURN loReturn
		
			CASE PCOUNT() = 1
				RETURN loReturn
		
			CASE PCOUNT() = 2
				tlFreeze    = True
				tlSaveWB    = True
				tcSheetName = this.DeriveSheetName(txWB)
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 3
				tlSaveWB    = True
				tcSheetName = this.DeriveSheetName(txWB)
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 4
				tcSheetName = this.DeriveSheetName(txWB)
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
			
			CASE PCOUNT() = 5
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tlDynamicProperties = False
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 6
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 7
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 8
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 9
				tnBegCol = 1
		ENDCASE
		IF VARTYPE(tcSheetName) != 'C' .OR. EMPTY(tcSheetName)
			tcSheetName = this.DeriveSheetName(txWB)
		ENDIF
		this.SaveVFPSettings()
		DO CASE
			CASE VARTYPE(txWB) = "C"
				lnWB = this.CreateWorkbook(txWB)
				IF lnWB = 0
					lnWB = this.GetWorkbook(txWB)
				ENDIF
		
			CASE VARTYPE(txWB) = "N"
				IF SEEK(BINTOC(txWB), "xl_workbooks", "workbook")
					lnWB = txWB
				ELSE
					this.RestoreVFPSettings()
					RETURN loReturn
				ENDIF
		
			OTHERWISE
				this.RestoreVFPSettings()
				RETURN loReturn
		ENDCASE
		IF lnWB > 0
			IF tnSheet = 0
				lnSh = this.AddSheet(lnWB, tcSheetName)
			ELSE
				IF this.GetSheetRecord(lnWB, tnSheet)
					lnSh = tnSheet
				ELSE
					RETURN loReturn
				ENDIF
			ENDIF
			IF lnSh > 0
				this.SetDisplayGridLines(lnWB, lnSh, tlShowGridLines)
		*-*		Get the table/cursor to output and schema
				lcGridAlias = JUSTSTEM(toGrid.RecordSource)
				lnFldCnt = AFIELDS(laTblFields, lcGridAlias)
		
		*-*		Get the record count and display a status window
				SELECT COUNT(*) FROM &lcGridAlias INTO ARRAY laRowCount
				RAISEEVENT(this, "OnShowStatusMessage", 3, 0, NVL(laRowCount[1], 0))
		
		*-*		Get the number of columns to process and
		*-*		Get the column output order based on grid display order                       && Change requested by Matt Slay to output by grid display order and visible setting
				IF tlInclHiddenCols
					lnColCount = toGrid.ColumnCount
					DIMENSION laColOrder[lnColCount, 2]
					FOR lnCol=1 TO toGrid.ColumnCount
						laColOrder[lnCol, 1] = toGrid.Columns(lnCol).ColumnOrder              && Grid display order
						laColOrder[lnCol, 2] = lnCol                                          && Column property order
					ENDFOR
				ELSE
					lnColCount = 0
					FOR lnCol=1 TO toGrid.ColumnCount
						IF toGrid.Columns(lnCol).Visible
							lnColCount = lnColCount + 1
							DIMENSION laColOrder[lnColCount, 2]
							laColOrder[lnColCount, 1] = toGrid.Columns(lnCol).ColumnOrder     && Grid display order
							laColOrder[lnColCount, 2] = lnCol                                 && Column property order
						ENDIF
					ENDFOR
				ENDIF
				IF lnColCount = 0
					this.RestoreVFPSettings()
					RETURN False
				ENDIF
				ASORT(laColOrder)
		
		*-*		Make a copy of the grid object's properties for formatting output
				loGrid = CREATEOBJECT("Empty")
				ADDPROPERTY(loGrid, "ColumnCount", lnColCount)
				ADDPROPERTY(loGrid, "Columns[lnColCount]")
				
				FOR lnCol=1 TO loGrid.ColumnCount
					loGrid.Columns(lnCol) = this.GetColumnObject(toGrid.Columns(laColOrder[lnCol, 2]), @laTblFields)
				ENDFOR
		
		*-*		Get the cell format definitions for the columns and create a cell style definition for each column
				lnNumFmtsId = 0
				FOR lnCol=1 TO loGrid.ColumnCount
					this.GetGridColumnFormat(loGrid.Columns(lnCol), lnNumFmtsId, lnWB)   && loGrid column object passed by reference
		
					lnXfsId = this.CreateFormatStyle(lnWB)
					this.AddStyleFont(lnWB, lnXfsId, loGrid.Columns(lnCol).FontName, loGrid.Columns(lnCol).FontSize, True, False)
					loGrid.Columns(lnCol).HeaderStyleId = lnXfsId
		
					lnXfsId = this.CreateFormatStyle(lnWB)
					this.AddStyleFont(lnWB, lnXfsId, loGrid.Columns(lnCol).FontName, loGrid.Columns(lnCol).FontSize, loGrid.Columns(lnCol).FontBold, loGrid.Columns(lnCol).FontItalic, loGrid.Columns(lnCol).ForeColor)
					IF loGrid.Columns(lnCol).NumFmtsIndex >= START_NUMERIC_FORMAT_ID
						INSERT INTO xl_numFmts (workbook, id, formatxml, formatcode, applydec) ;
							VALUES (lnWB, loGrid.Columns(lnCol).NumFmtsIndex, loGrid.Columns(lnCol).FormatCode, this.GetStringXML(loGrid.Columns(lnCol).FormatCode), False)
					ENDIF
					this.AddStyleNumericFormat(lnWB, lnXfsId, loGrid.Columns(lnCol).NumFmtsIndex)
					loGrid.Columns(lnCol).ValueStyleId = lnXfsId
				ENDFOR
		
		*-*		Add the header row if defined and set the column widths; get the column font info
				IF toGrid.HeaderHeight > 0                                                    && Change recommendation by Doug Hennig (if no headers, start in first row)
					lnRow = tnBegRow
					FOR lnCol=1 TO lnColCount
						loColumn = toGrid.Columns(laColOrder[lnCol, 2])
						this.SetCellValue(lnWB, lnSh, lnRow, lnCol+tnBegCol-1, loColumn.Header1.Caption)
						this.SetCellStyle(lnWB, lnSh, lnRow, lnCol+tnBegCol-1, loGrid.Columns(laColOrder[lnCol, 2]).HeaderStyleId)
						this.SetColumnWidth(lnWB, lnSh, lnCol+tnBegCol-1, this.ConvertPixelsToExcelUnits(loColumn.Width))
					ENDFOR
				ELSE
					lnRow = tnBegRow - 1
					FOR lnCol=1 TO lnColCount
						loColumn = toGrid.Columns(laColOrder[lnCol, 2])
						this.SetColumnWidth(lnWB, lnSh, lnCol+tnBegCol-1, this.ConvertPixelsToExcelUnits(loColumn.Width))
					ENDFOR
				ENDIF
		
		*-*		Add the cell data values and cell formatting
				SELECT &lcGridAlias
				SCAN
					lnRow = lnRow + 1
					RAISEEVENT(this, "OnShowStatusMessage", 3, lnRow)
					FOR lnCol=1 TO lnColCount
						loColumn = toGrid.Columns(laColOrder[lnCol, 2])
						lcField  = loColumn.ControlSource
						lxCellValue = EVALUATE(lcField)
						IF loGrid.Columns(laColOrder[lnCol, 2]).FieldPercent
							lxCellValue = lxCellValue / 100
						ENDIF
						this.SetCellValue(lnWB, lnSh, lnRow, lnCol+tnBegCol-1, lxCellValue)
		
						IF loGrid.Columns(laColOrder[lnCol, 2]).DynamicProperties
							TRY
								lnFontColor = EVALUATE(loColumn.DynamicForeColor)
								IF !VARTYPE(lnFontColor) = "N"
									lnFontColor = loColumn.ForeColor
								ENDIF
							CATCH TO loException
								lnFontColor = loColumn.ForeColor
							ENDTRY
							TRY
								lcFontName = EVALUATE(loColumn.DynamicFontName)
								IF !VARTYPE(lcFontName) = "C"
									lcFontName = loColumn.FontName
								ENDIF
							CATCH TO loException
								lcFontName = loColumn.FontName
							ENDTRY
							TRY
								lnFontSize = EVALUATE(loColumn.DynamicFontSize)
								IF !VARTYPE(lnFontSize) = "N"
									lnFontSize = loColumn.FontSize
								ENDIF
							CATCH TO loException
								lnFontSize = loColumn.FontSize
							ENDTRY
							TRY
								llFontBold = EVALUATE(loColumn.DynamicFontBold)
								IF !VARTYPE(llFontBold) = "L"
									llFontBold = loColumn.FontBold
								ENDIF
							CATCH TO loException
								llFontBold = loColumn.FontBold
							ENDTRY
							TRY
								llFontItalic = EVALUATE(loColumn.DynamicFontItalic)
								IF !VARTYPE(llFontItalic) = "L"
									llFontItalic = loColumn.FontItalic
								ENDIF
							CATCH TO loException
								llFontItalic = loColumn.FontItalic
							ENDTRY
							TRY
								lcFontUnderLine = EVALUATE(loColumn.DynamicFontUnderLine)
								IF !VARTYPE(lcFontUnderLine) = "L"
									lcFontUnderLine = loColumn.FontUnderLine
								ENDIF
							CATCH TO loException
								lcFontUnderLine = loColumn.FontUnderLine
							ENDTRY
							TRY
								llFontStrikeThru = EVALUATE(loColumn.DynamicFontStrikeThru)
								IF !VARTYPE(llFontStrikeThru) = "L"
									llFontStrikeThru = loColumn.FontStrikeThru
								ENDIF
							CATCH TO loException
								llFontStrikeThru = loColumn.FontStrikeThru
							ENDTRY
							TRY
								lnFillColor = EVALUATE(loColumn.DynamicBackColor)
								IF VARTYPE(lnFillColor) != 'N'
									lnFillColor = RGB(255,255,255)
								ENDIF
							CATCH TO loException
								lnFillColor = RGB(255,255,255)
							ENDTRY
							lnCellXfsId = this.GetStyleFormatId(lnWB, loGrid.Columns(laColOrder[lnCol, 2]).NumFmtsIndex, lnFillColor, lcFontName, lnFontSize, llFontBold, ;
																llFontItalic, lnFontColor, lcFontUnderLine, llFontStrikeThru, FONT_VERTICAL_BASELINE)
						ELSE
							lnCellXfsId = loGrid.Columns(laColOrder[lnCol, 2]).ValueStyleId
						ENDIF
						this.SetCellStyle(lnWB, lnSh, lnRow, lnCol+tnBegCol-1, lnCellXfsId)
					ENDFOR
				ENDSCAN
		
		*-*		Freeze the first row if specified
				IF tlFreeze
					this.FreezePanes(lnWB, lnSh, 1, 0)
				ENDIF
		
				RAISEEVENT(this, "OnShowStatusMessage", 3, -1)
				IF tlSaveWB
					this.SaveWorkbook(lnWB)
				ENDIF
				loReturn.Sheet    = lnSh
				loReturn.Workbook = lnWB
			ENDIF
		ENDIF
		this.RestoreVFPSettings()
		RETURN loReturn
	ENDPROC

	PROCEDURE SaveGridToWorkbookEx		&& Saves the passed grid to a workbook without adding the content to the internal cursors
		LPARAMETERS toGrid, tcFileName, tlFreeze, tcSheetName, tlInclHiddenCols, tlShowGridLines, tnBegRow, tnBegCol
		LOCAL llError, lcTempPath, lcGridAlias, lcFileName, lhFile, lcRowDefHt, lnCol, lnRow, lcSETCentury, lxCellValue
		LOCAL lnColCount, loGrid, loColumn, lnFmtId, lcCol, lcFormatCode, lnDataRow, lcFormula, lnNdx, lcFieldName
		LOCAL lnNumFmtsId, loException, llSuccess, lnWB, lnFldCnt, lcColRow, lcRow
		LOCAL ARRAY laColOrder[1], laSheetNames[1], laLastWB[1], laTblFields[1]
		llError = False
		IF VARTYPE(toGrid) != "O" .AND. PEMSTATUS(toGrid, "BaseClass", 5) .AND. LOWER(toGrid.BaseClass) != "grid"
			RETURN False
		ENDIF
		DO CASE
			CASE PCOUNT() < 2
				RETURN False
		
			CASE PCOUNT() = 2
				tlFreeze     = True
				tcSheetName  = toGrid.Name
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 3
				tcSheetName  = toGrid.Name
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 4
				tlInclHiddenCols = True
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 5
				tlShowGridLines  = IIF(toGrid.GridLines=0, False, True)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 6
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 7
				tnBegCol = 1
		ENDCASE
		IF VARTYPE(tcSheetName) != 'C' .OR. EMPTY(tcSheetName)
			tcSheetName = toGrid.Name
		ENDIF
		tcSheetName = this.CheckSheetName(tcSheetName)
		IF ISNULL(tcSheetName)
			RETURN False
		ENDIF
		
		*-* Set temporary output path
		lcTempPath = this.CreateWorkingDirectories()
		IF ISNULL(lcTempPath)
			RETURN False
		ENDIF
		
		*-*	Save current settings and then set to US formats
		this.SaveVFPSettings()
		
		*-*	Get the table/cursor to output and schema
		lcGridAlias = JUSTSTEM(toGrid.RecordSource)
		lnFldCnt = AFIELDS(laTblFields, lcGridAlias)
		
		*-*	Get the number of columns to process
		*-*	Get the column output order based on grid display order                          && Change requested by Matt Slay to output by grid display order and visible setting
		IF tlInclHiddenCols
			lnColCount = toGrid.ColumnCount
			DIMENSION laColOrder[lnColCount, 2]
			FOR lnCol=1 TO toGrid.ColumnCount
				laColOrder[lnCol, 1] = toGrid.Columns(lnCol).ColumnOrder                     && Grid display order
				laColOrder[lnCol, 2] = lnCol                                                 && Column property order
			ENDFOR
		ELSE
			lnColCount = 0
			FOR lnCol=1 TO toGrid.ColumnCount
				IF toGrid.Columns(lnCol).Visible
					lnColCount = lnColCount + 1
					DIMENSION laColOrder[lnColCount, 2]
					laColOrder[lnColCount, 1] = toGrid.Columns(lnCol).ColumnOrder             && Grid display order
					laColOrder[lnColCount, 2] = lnCol                                         && Column property order
				ENDIF
			ENDFOR
		ENDIF
		IF lnColCount = 0
			this.RestoreVFPSettings()
			RETURN False
		ENDIF
		ASORT(laColOrder)
		
		*-*	Make a copy of the grid object's properties for formatting output
		loGrid = CREATEOBJECT("Empty")
		ADDPROPERTY(loGrid, "AddSumRow", False)
		ADDPROPERTY(loGrid, "ColumnCount", lnColCount)
		ADDPROPERTY(loGrid, "Columns[lnColCount]")
		
		FOR lnCol=1 TO loGrid.ColumnCount
			loGrid.Columns(lnCol) = this.GetColumnObject(toGrid.Columns(laColOrder[lnCol, 2]), @laTblFields)
			loGrid.AddSumRow = IIF(loGrid.Columns(lnCol).SumColumn, True, loGrid.AddSumRow)
		ENDFOR
		
		*-*	Create the workbook xml files
		TRY
		*-*	Create the styles xml
			lcFileName = lcTempPath + "xl\styles.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile =< 0
				THROW "Unable to create the styles.xml in the temporary file location"
			ENDIF
		
		*-*	Write the header section
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ')
			FWRITE(lhFile, 'xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" ')
			FWRITE(lhFile, 'mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
		
		*-*	Get the cell format definitions for the columns
			SELECT MAX(workbook) FROM xl_workbooks INTO ARRAY laLastWB
			lnWB = EVL(NVL(laLastWB[1], 0), 0) + 1                       && Change suggested by Doug Hennig to handle a .F. value as well as .NULL. value
			DIMENSION this.LastNumFmtId[lnWB, 2]
			this.LastNumFmtId[lnWB, 1] = lnWB
			this.LastNumFmtId[lnWB, 2] = START_NUMERIC_FORMAT_ID
		
			lnNumFmtsId = 0
			FOR lnCol=1 TO loGrid.ColumnCount
				lnNumFmtsId = this.GetGridColumnFormat(loGrid.Columns(lnCol), lnNumFmtsId, lnWB)   && loGrid column object passed by reference
			ENDFOR
		
		*-*	Write the Number format section
			IF lnNumFmtsId > 0
				FWRITE(lhFile, '<numFmts count="' + TRANSFORM(lnNumFmtsId) + '">')
				FOR lnCol=1 TO loGrid.ColumnCount
					IF loGrid.Columns(lnCol).NumFmtsIndex >= START_NUMERIC_FORMAT_ID
						FWRITE(lhFile, '<numFmt numFmtId="' + TRANSFORM(loGrid.Columns(lnCol).NumFmtsIndex) + '" formatCode="' + loGrid.Columns(lnCol).FormatCode + '"/>')
					ENDIF
				ENDFOR
				FWRITE(lhFile, '</numFmts>')
			ENDIF
		
		*-*	Write the fonts section
			FWRITE(lhFile, '<fonts count="' + TRANSFORM(2*loGrid.ColumnCount+1) + '" x14ac:knownFonts="1">')
			FWRITE(lhFile, '<font><name val="Calibri"/><sz val="11"/><color theme="1"/><family val="2"/><scheme val="minor"/></font>')
			FOR lnCol=1 TO loGrid.ColumnCount             && Write column header fonts
				FWRITE(lhFile, '<font>')
				FWRITE(lhFile, '<name val="' + loGrid.Columns(lnCol).HeaderFontName + '"/><sz val="' + TRANSFORM(loGrid.Columns(lnCol).HeaderFontSize) + '"/>')
				FWRITE(lhFile, IIF(loGrid.Columns(lnCol).HeaderFontBold, '<b/>', ''))
				FWRITE(lhFile, IIF(loGrid.Columns(lnCol).HeaderFontItalic, '<i/>', ''))
				FWRITE(lhFile, '</font>')
			ENDFOR
		
			FOR lnCol=1 TO loGrid.ColumnCount             && Write column cell fonts
				FWRITE(lhFile, '<font>')
				FWRITE(lhFile, '<name val="' + loGrid.Columns(lnCol).FontName + '"/><sz val="' + TRANSFORM(loGrid.Columns(lnCol).FontSize) + '"/>')
				FWRITE(lhFile, IIF(loGrid.Columns(lnCol).FontBold, '<b/>', ''))
				FWRITE(lhFile, IIF(loGrid.Columns(lnCol).FontItalic, '<i/>', ''))
				FWRITE(lhFile, '</font>')
			ENDFOR
			FWRITE(lhFile, '</fonts>')
		
		*-*	Write the cell fills, borders and stylexfs sections (standard entries)
			FWRITE(lhFile, '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>')
			FWRITE(lhFile, '<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>')
			FWRITE(lhFile, '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>')
		
		*-*	Write the cell XFS format reference section
			FWRITE(lhFile, '<cellXfs count="' + TRANSFORM(2*loGrid.ColumnCount+1) + '">')
			FWRITE(lhFile, '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>')
			FOR lnCol=1 TO loGrid.ColumnCount             && Write column header format references
				FWRITE(lhFile, '<xf numFmtId="0" fontId="' + TRANSFORM(lnCol) + '" fillId="0" borderId="0" xfId="0" applyFont="1"/>')
			ENDFOR
			FOR lnCol=1 TO loGrid.ColumnCount             && Write column cell format references
				FWRITE(lhFile, '<xf numFmtId="' + TRANSFORM(loGrid.Columns(lnCol).NumFmtsIndex) + '" fontId="' + TRANSFORM(lnCol+loGrid.ColumnCount) + '" fillId="0" borderId="0" xfId="0" applyFont="1"')
				FWRITE(lhFile, IIF(loGrid.Columns(lnCol).NumFmtsIndex > 0, ' applyNumberFormat="1"/>', '/>'))
			ENDFOR
			FWRITE(lhFile, '</cellXfs>')
		
		*-*	Write the remaining sections
			FWRITE(lhFile, '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>')
			FWRITE(lhFile, '</styleSheet>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		*-*	Create the remaining support xmls
			DIMENSION laSheetNames[1]
			laSheetNames[1] = tcSheetName
			IF !this.WriteDirectXMLs(lcTempPath, @laSheetNames, 1)
				THROW "Unable to create the supporting xmls in the temporary file location"
			ENDIF
		
		*-*	Write the sheet XML
			lcFileName = lcTempPath + "xl\worksheets\sheet1.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile <= 0
				THROW "Unable to create the sheet1.xml in the temporary file location"
			ENDIF
			
		*-*	Write sheet XML header information
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"')
			FWRITE(lhFile, ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"')
			FWRITE(lhFile, ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"')
			FWRITE(lhFile, ' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
		
		*-*	Write the sheet view information which includes the freeze pane information
			FWRITE(lhFile, '<dimension ref="A1:' + this.ColumnIndexToAscii(loGrid.ColumnCount) + TRANSFORM(RECCOUNT(lcGridAlias)+1) + '"/>')
			IF tlFreeze
				lcRow = TRANSFORM(tnBegRow+1)
				FWRITE(lhFile, '<sheetViews><sheetView workbookViewId="0" showGridLines="' + IIF(tlShowGridLines, '1', '0') + '">')
				FWRITE(lhFile, '<pane state="frozenSplit" activePane="bottomLeft" topLeftCell="A' + lcRow + '" ySplit="' + TRANSFORM(tnBegRow) + '"/>')
				FWRITE(lhFile, '<selection activeCell="A' + lcRow + '" sqref="A' + lcRow + '" pane="bottomLeft"/>')
				FWRITE(lhFile, '</sheetView></sheetViews>')
			ELSE
				FWRITE(lhFile, '<sheetViews><sheetView workbookViewId="0" showGridLines="' + IIF(tlShowGridLines, '1', '0') + '"/>')
				FWRITE(lhFile, '</sheetViews>')
			ENDIF
			FWRITE(lhFile, '<sheetFormatPr defaultRowHeight="15"/>')
		
		*-*	Write sheet Column widths
			FWRITE(lhFile, '<cols>')
			FOR lnCol=1 TO loGrid.ColumnCount
				FWRITE(lhFile, '<col min="' + TRANSFORM(lnCol+tnBegCol-1) + '" max="' + TRANSFORM(lnCol+tnBegCol-1) + '" width="' + TRANSFORM(loGrid.Columns(lnCol).Width) + '" customWidth="1"/>')
			ENDFOR
			FWRITE(lhFile, '</cols>')
		
		*-*	Write sheet cell data values
			lcRowDefHt = ' ht="30"'
			FWRITE(lhFile, '<sheetData>')
			
		*-*	Write column names in the first row
			IF toGrid.HeaderHeight > 0                                               && Change recommendation by Doug Hennig (if no headers, then no output)
				lnRow = tnBegRow
				FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="' + TRANSFORM(tnBegCol) + ':' + TRANSFORM(loGrid.ColumnCount+tnBegCol-1) + '"' + lcRowDefHt + '>')
				FOR lnCol=1 TO loGrid.ColumnCount
					FWRITE(lhFile, '<c r="' + this.ColumnIndexToAscii(lnCol+tnBegCol-1) + TRANSFORM(lnRow) + '" s="' + TRANSFORM(lnCol) + '" t="inlineStr">')
					FWRITE(lhFile, '<is><t>' + this.GetXMLString(loGrid.Columns(lnCol).HeaderCaption) + '</t></is>')
					FWRITE(lhFile, '</c>')
				ENDFOR
				FWRITE(lhFile, '</row>')
			ELSE
				lnRow = tnBegRow - 1
			ENDIF
			lnDataRow = lnRow + 1
		
		*-*	Write the grid field values to cells
			lcSETCentury = SET("CENTURY")
			SET CENTURY ON
			SELECT &lcGridAlias
			SCAN
				lnRow = lnRow + 1
				FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="' + TRANSFORM(tnBegCol) + ':' + TRANSFORM(loGrid.ColumnCount+tnBegCol-1) + '"' + lcRowDefHt + '>')
				FOR lnCol=1 TO loGrid.ColumnCount
					lnFmtId = lnCol + loGrid.ColumnCount
					TRY
						lxCellValue = NVL(EVALUATE(loGrid.Columns(lnCol).ControlSource), "")
						IF loGrid.Columns(lnCol).FieldPercent
							lxCellValue = lxCellValue / 100
						ENDIF
		
					CATCH TO loException
						lxCellValue = .NULL.
					ENDTRY
					IF ISNULL(lxCellValue)
						lxCellValue = this.NullValue
					ELSE
						loGrid.Columns(lnCol).FieldType = VARTYPE(lxCellValue)     && Correction provided by vadpost on VFPx to handle calculated field values
					ENDIF
					lcColRow = this.ColumnIndexToAscii(lnCol+tnBegCol-1) + TRANSFORM(lnRow)
					DO CASE
						CASE ISNULL(lxCellValue) .OR. (EMPTY(lxCellValue) ;
							 .AND. (!INLIST(loGrid.Columns(lnCol).FieldType, FIELD_TYPE_INT, FIELD_TYPE_FLOAT, FIELD_TYPE_NUMERIC, FIELD_TYPE_DOUBLE, FIELD_TYPE_CURRENCY, FIELD_TYPE_LOGICAL) ;
							 .OR.  loGrid.Columns(lnCol).Format = 'Z'))           && Apply formatting to cell only (change submitted by Doug Hennig)
							FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '"></c>')
		
						CASE loGrid.Columns(lnCol).FieldType = FIELD_TYPE_DATE
							IF lxCellValue >= DATE(1900, 1, 1)
								lxCellValue = TRANSFORM(INT(lxCellValue - DATE(1900, 1, 1) + 2))
								FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="n">')
								FWRITE(lhFile, '<v>' + TRANSFORM(lxCellValue) + '</v></c>')
							ELSE
								lxCellValue = TRANSFORM(lxCellValue)
								FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="inlineStr">')
								FWRITE(lhFile, '<is><t>' + this.GetXMLString(lxCellValue) + '</t></is>')
								FWRITE(lhFile, '</c>')
							ENDIF
		
						CASE loGrid.Columns(lnCol).FieldType = FIELD_TYPE_DATETIME
							IF TTOD(lxCellValue) >= DATE(1900, 1, 1)
								lnSetDecimals = SET("DECIMALS")
								SET DECIMALS TO 12
								lnWholePart = INT(TTOD(lxCellValue) - DATE(1900, 1, 1) + 2)
								lnFractPart = ((HOUR(lxCellValue) * 60 + MINUTE(lxCellValue)) * 60 + SEC(lxCellValue)) / (24 * 60 * 60)
								lxCellValue = TRANSFORM(lnWholePart + lnFractPart)
								FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="n">')
								FWRITE(lhFile, '<v>' + TRANSFORM(lxCellValue) + '</v></c>')
								SET DECIMALS TO (lnSetDecimals)
							ELSE
								lxCellValue = TRANSFORM(lxCellValue)
								FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="inlineStr">')
								FWRITE(lhFile, '<is><t>' + this.GetXMLString(lxCellValue) + '</t></is>')
								FWRITE(lhFile, '</c>')
							ENDIF
		
						CASE loGrid.Columns(lnCol).FieldType = FIELD_TYPE_LOGICAL
							lxCellValue = IIF(lxCellValue, GETWORDNUM(this.TrueFalseValue, 1, "|"), GETWORDNUM(this.TrueFalseValue, 2, "|"))
							FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="inlineStr">')
							FWRITE(lhFile, '<is><t>' + this.GetXMLString(lxCellValue) + '</t></is>')
							FWRITE(lhFile, '</c>')
		
						CASE loGrid.Columns(lnCol).FieldType = FIELD_TYPE_CURRENCY
							FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="n">')
							FWRITE(lhFile, '<v>' + TRANSFORM(MTON(lxCellValue)) + '</v></c>')
		
						CASE INLIST(loGrid.Columns(lnCol).FieldType, FIELD_TYPE_CHAR, FIELD_TYPE_VCHAR, FIELD_TYPE_MEMO)
							FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="inlineStr">')
							FWRITE(lhFile, '<is><t>' + this.GetXMLString(ALLTRIM(lxCellValue)) + '</t></is>')
							FWRITE(lhFile, '</c>')
		
						CASE INLIST(loGrid.Columns(lnCol).FieldType, FIELD_TYPE_INT, FIELD_TYPE_FLOAT, FIELD_TYPE_NUMERIC, FIELD_TYPE_DOUBLE)
							FWRITE(lhFile, '<c r="' + lcColRow + '" s="' + TRANSFORM(lnFmtId) + '" t="n">')
							FWRITE(lhFile, '<v>' + TRANSFORM(lxCellValue) + '</v></c>')
		
						OTHERWISE
							FWRITE(lhFile, '<c r="' + lcColRow + '" t="inlineStr">')
							FWRITE(lhFile, '<is><t>Unsupported Field Type</t></is></c>')
					ENDCASE
				ENDFOR
				FWRITE(lhFile, '</row>')
			ENDSCAN
		
		*-*	Add column sum function if entered for selected columns
			IF loGrid.AddSumRow
				lnRow = lnRow + 1
				FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="' + TRANSFORM(tnBegCol) + ':' + TRANSFORM(loGrid.ColumnCount+tnBegCol-1) + '"' + lcRowDefHt + '>')
				FOR lnCol=1 TO loGrid.ColumnCount
					lcCol   = this.ColumnIndexToAscii(lnCol+tnBegCol-1)
					lnFmtId = lnCol + loGrid.ColumnCount
					IF loGrid.Columns(lnCol).SumColumn
						lcFormula = '=SUM(' + lcCol + TRANSFORM(lnDataRow) + ":"  + lcCol + TRANSFORM(lnRow-1) + ")"
						FWRITE(lhFile, '<c r="' + lcCol + TRANSFORM(lnRow) + '" s="' + TRANSFORM(lnFmtId) + '" t="str"' + '>')
						FWRITE(lhFile, '<f>' + lcFormula + '</f>')
					ELSE
						FWRITE(lhFile, '<c r="' + lcCol + TRANSFORM(lnRow) + '" s="' + TRANSFORM(lnFmtId) + '">')
					ENDIF
					FWRITE(lhFile, '</c>')
				ENDFOR
				FWRITE(lhFile, '</row>')
			ENDIF
			SET CENTURY &lcSETCentury
		
		*-*	End the sheet cell data values and worksheet
			FWRITE(lhFile, '</sheetData>')
			FWRITE(lhFile, '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>')
			FWRITE(lhFile, '</worksheet>')
		
		CATCH TO loException
			SET STEP ON
			llError = True
			this.ErrorLevelId = 14
			RAISEEVENT(this, "OnShowErrorMessage", 14, loException.message)
		
		FINALLY
			IF lhFile > 0
				FCLOSE(lhFile)
				this.SaveAsUTF8(lcFileName)
			ENDIF
		*-*	Remove the numFmtId for this temporary workbook
			lnWB = lnWB - 1
			IF lnWB > 0
				DIMENSION this.LastNumFmtId[lnWB, 2]
			ELSE
				this.LastNumFmtId[1, 1] = 0
				this.LastNumFmtId[1, 2] = START_NUMERIC_FORMAT_ID
			ENDIF
		ENDTRY
		IF llError
			llSuccess = False
		ELSE
			llSuccess = this.CreateExcelFile(lcTempPath, JUSTPATH(tcFileName), JUSTSTEM(tcFileName))    && Change recommendation by Doug Hennig [placement of code after ELSE]
		ENDIF
		this.RemoveWorkingDirectories(lcTempPath)
		
		*-*	Restore default settings
		this.RestoreVFPSettings()
		RETURN llSuccess
	ENDPROC

	PROCEDURE SaveMultiGridToWorkbookEx		&& Same as SaveGridToWorkbookEx() method but handles multiple grids being passed
		LPARAMETERS toGrids, tcFileName
		LOCAL llError, lcTempPath, lcAlias, lcFileName, lhFile, lcRowDefHt, lnCol, lnRow, lcSETCentury, lxCellValue
		LOCAL lnColCount, lnColWidth, lcCurAlias, lnGrid, loGrid, loException, lcSheetName
		LOCAL ARRAY laColOrder[1], laSheetNames[1]
		llError    = False
		lcCurAlias = ALIAS()
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		IF VARTYPE(toGrids) = "O"
			IF PEMSTATUS(toGrids, "Count", 5) .AND. VARTYPE(toGrids.Count) != "N"
				RETURN False
			ELSE
				IF toGrids.Count < 1
					RETURN False
				ENDIF
			ENDIF
		
			IF PEMSTATUS(toGrids, "List", 5)
				llError = False
				TRY
					IF ALEN(toGrids.List, 2) != 4   && Column 1 Grid; Column 2  SheetName; Column 3 Freeze indicator, Column 4 HiddenCols indicator
						THROW
					ENDIF
				CATCH TO loException
					llError = True
				ENDTRY
				IF llError
					RETURN False
				ENDIF
				FOR lnGrid=1 TO toGrids.Count
		*-*			Validate grid object
					loGrid = toGrids.List[lnGrid, 1]
					IF VARTYPE(loGrid) != "O" .AND. PEMSTATUS(loGrid, "BaseClass", 5) .AND. LOWER(loGrid.BaseClass) != "grid"
						RETURN False
					ENDIF
		
		*-*			Validate sheet name
					lcSheetName = toGrids.List[lnGrid, 2]
					IF VARTYPE(lcSheetName) != 'C' .OR. EMPTY(lcSheetName)
						lcSheetName = loGrid.Name
					ENDIF
					lcSheetName = this.CheckSheetName(lcSheetName)
					IF ISNULL(lcSheetName)
						RETURN False
					ENDIF
					toGrids.List[lnGrid, 2] = lcSheetName
		
		*-*			Validate freeze indicator
					IF VARTYPE(toGrids.List[lnGrid, 3]) != 'L'
					 	toGrids.List[lnGrid, 3] = False
					ENDIF
		
		*-*			Validate hidden column indicator
					IF VARTYPE(toGrids.List[lnGrid, 4]) != 'L'
						toGrids.List[lnGrid, 4] = False
					ENDIF
				ENDFOR
			ENDIF
		ELSE
			RETURN False
		ENDIF
		
		*-* Set temporary output path
		lcTempPath = this.CreateWorkingDirectories()
		IF ISNULL(lcTempPath)
			RETURN False
		ENDIF
		
		*-*	Save current settings and then set to US formats
		lcSetPoint  = SET("POINT")
		lcSeparator = SET("SEPARATOR")
		IF lcSetPoint != "."
			SET POINT TO "."
			SET SEPARATOR TO ","
		ENDIF
		
		*-*	Output the workbook
		DIMENSION laSheetNames[toGrids.Count]
		FOR lnGrid=1 TO toGrids.Count
			laSheetNames[lnGrid] = toGrids.List[lnGrid, 2]
		ENDFOR
		
		
		
		IF !this.WriteDirectXMLs(lcTempPath, @laSheetNames, toGrids.Count)
			RETURN False
		ENDIF
		
		
		
		
		*-*	Write the sheet xml for each grid
		FOR lnGrid=1 TO toGrids.Count
			loGrid  = toGrids.List[lnGrid, 1]
			lcAlias = JUSTSTEM(loGrid.RecordSource)
			
			*-*	Get the number of columns to process
			*-*	Get the column output order based on grid display order       && Change requested by Matt Slay to output by grid display order and visible setting
			IF toGrids.List[lnGrid, 4]
				lnColCount = loGrid.ColumnCount
				DIMENSION laColOrder[lnColCount, 2]
				FOR lnCol=1 TO loGrid.ColumnCount
					laColOrder[lnCol, 1] = loGrid.Columns(lnCol).ColumnOrder     && Grid display order
					laColOrder[lnCol, 2] = lnCol                                 && Column property order
				ENDFOR
			ELSE
				lnColCount = 0
				FOR lnCol=1 TO loGrid.ColumnCount
					IF loGrid.Columns(lnCol).Visible
						lnColCount = lnColCount + 1
						DIMENSION laColOrder[lnColCount, 2]
						laColOrder[lnColCount, 1] = loGrid.Columns(lnCol).ColumnOrder   && Grid display order
						laColOrder[lnColCount, 2] = lnCol                        && Column property order
					ENDIF
				ENDFOR
			ENDIF
			IF lnColCount = 0
				RETURN False
			ENDIF
			ASORT(laColOrder)
			
			TRY
				lcFileName = lcTempPath + "xl\worksheets\sheet" + TRANSFORM(lnGrid) + ".xml"
				lhFile = FCREATE(lcFileName)
				IF lhFile < 0
					THROW
				ENDIF
		
		*-*		Write sheet XML header information
				FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
				FWRITE(lhFile, '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"')
				FWRITE(lhFile, ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"')
				FWRITE(lhFile, ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"')
				FWRITE(lhFile, ' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
		
		*-*		Write the sheet view information which includes the freeze pane information
				FWRITE(lhFile, '<dimension ref="A1:' + this.ColumnIndexToAscii(lnColCount) + TRANSFORM(RECCOUNT(lcAlias)+1) + '"/>')
				IF toGrids.List[lnGrid, 3]
					FWRITE(lhFile, '<sheetViews><sheetView workbookViewId="0">')
					FWRITE(lhFile, '<pane state="frozenSplit" activePane="bottomLeft" topLeftCell="A2" ySplit="1"/>')
					FWRITE(lhFile, '<selection activeCell="A2" sqref="A2" pane="bottomLeft"/>')
					FWRITE(lhFile, '</sheetView></sheetViews>')
				ENDIF
				FWRITE(lhFile, '<sheetFormatPr defaultRowHeight="15"/>')
		
		*-*		Write sheet Column widths
				FWRITE(lhFile, '<cols>')
				FOR lnCol=1 TO lnColCount
					lnColWidth = this.ConvertPixelsToExcelUnits(loGrid.Columns(laColOrder[lnCol, 2]).Width)
					FWRITE(lhFile, '<col min="' + TRANSFORM(lnCol) + '" max="' + TRANSFORM(lnCol) + '" width="' + TRANSFORM(lnColWidth+0.7109375) + '" customWidth="1"/>')
				ENDFOR
				FWRITE(lhFile, '</cols>')
		
		*-*		Write sheet cell data values
				lcRowDefHt = ' ht="30"'
				FWRITE(lhFile, '<sheetData>')
		
		*-*		Place field names in the first row
				IF loGrid.HeaderHeight > 0                                   && Change recommendation by Doug Hennig (if no headers, then no output)
					FWRITE(lhFile, '<row r="1" spans="1:' + TRANSFORM(lnColCount) + '"' + lcRowDefHt + '>')
					IF this.DefaultFont = "Calibri" .AND. this.DefaultFontSize = 11
						FOR lnCol=1 TO lnColCount
							this.WriteAsInLineString(lhFile, 1, lnCol, loGrid.Columns(laColOrder[lnCol, 2]).Header1.Caption)
						ENDFOR
					ELSE
						FOR lnCol=1 TO lnColCount
							this.WriteAsInLineFormattedString(lhFile, 1, lnCol, loGrid.Columns(laColOrder[lnCol, 2]).Header1.Caption)
						ENDFOR
					ENDIF
					FWRITE(lhFile, '</row>')
					lnRow = 1
				ELSE
					lnRow = 0
				ENDIF
		
		*-*		Write the grid field values to cells
				lcSETCentury = SET("CENTURY")
				SET CENTURY ON
				SELECT &lcAlias
				IF this.DefaultFont = "Calibri" .AND. this.DefaultFontSize = 11
					SCAN
						lnRow = lnRow + 1
						FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="1:' + TRANSFORM(lnColCount) + '"' + lcRowDefHt + '>')
						FOR lnCol=1 TO lnColCount
							lxCellValue = EVALUATE(loGrid.Columns(laColOrder[lnCol, 2]).ControlSource)
							this.WriteDirectCellValue(lhFile, lnRow, lnCol, lxCellValue)
						ENDFOR
						FWRITE(lhFile, '</row>')
					ENDSCAN
				ELSE
					SCAN
						lnRow = lnRow + 1
						FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="1:' + TRANSFORM(lnColCount) + '"' + lcRowDefHt + '>')
						FOR lnCol=1 TO lnColCount
							lxCellValue = EVALUATE(loGrid.Columns(laColOrder[lnCol, 2]).ControlSource)
							this.WriteDirectCellFormattedValue(lhFile, lnRow, lnCol, lxCellValue)
						ENDFOR
						FWRITE(lhFile, '</row>')
					ENDSCAN
				ENDIF
				SET CENTURY &lcSETCentury
		
		*-*		End the sheet cell data values and worksheet
				FWRITE(lhFile, '</sheetData>')
				FWRITE(lhFile, '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>')
				FWRITE(lhFile, '</worksheet>')
		
			CATCH TO loException
				SET STEP ON
				llError = True
				this.ErrorLevelId = 14
				RAISEEVENT(this, "OnShowErrorMessage", 14, loException.message)
		
			FINALLY
				FCLOSE(lhFile)
				this.SaveAsUTF8(lcFileName)
			ENDTRY
		ENDFOR
		IF llError
			llSuccess = False
		ELSE
			llSuccess = this.CreateExcelFile(lcTempPath, JUSTPATH(tcFileName), JUSTSTEM(tcFileName))   && Change recommendation by Doug Hennig [placement of code after ELSE]
		ENDIF
		*-*	Cleanup
		this.RemoveWorkingDirectories(lcTempPath)
		IF !EMPTY(lcCurAlias)
			SELECT (lcCurAlias)
		ENDIF
		
		*-*	Restore default settings
		IF lcSetPoint != "."
			SET POINT TO (lcSetPoint)
			SET SEPARATOR TO (lcSeparator)
		ENDIF
		RETURN llSuccess
	ENDPROC

	PROCEDURE SaveTableToWorkbook		&& Saves the table passed (alias) as a workbook sheet
		LPARAMETERS tcAlias, tnWB, tlFreeze, tlSaveWB, tcSheetName, tnSheet, tnBegRow, tnBegCol
		LOCAL lcAlias, lnCol, lnWB, lnSh, lnRow, lcField, lnFldCnt, llError, llOpenedTbl, loException, loReturn
		LOCAL lnNdx, lcCaption, lcCol, lnCellStyle
		LOCAL ARRAY laFields[1]
		loReturn = CREATEOBJECT("Empty")
		ADDPROPERTY(loReturn, "Workbook",  0)
		ADDPROPERTY(loReturn, "Sheet", 0)
		DO CASE
			CASE PCOUNT() < 2
				RETURN loReturn
		
			CASE PCOUNT() = 2
				tlFreeze = True
				tlSaveWB = False
				tcSheetName = JUSTSTEM(tcAlias)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 3
				tlSaveWB = False
				tcSheetName = JUSTSTEM(tcAlias)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 4
				tcSheetName = JUSTSTEM(tcAlias)
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 5
				tnSheet  = 0
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 6
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 7
				tnBegCol = 1
		ENDCASE
		IF VARTYPE(tcSheetName) != 'C' .OR. EMPTY(tcSheetName)
			tcSheetName = this.DeriveSheetName(tnWB)
		ELSE
			tcSheetName = this.CheckSheetName(tcSheetName)
			IF ISNULL(tcSheetName)
				RETURN loReturn
			ENDIF
		ENDIF
		
		*-*	Save current settings and then set to US formats
		this.SaveVFPSettings()
		
		*-*	Select/Open the table to be exported
		DO CASE
			CASE USED(tcAlias)
				llOpenedTbl = False
				lcAlias = tcAlias
		
			CASE OCCURS("\", tcAlias) > 0
				TRY
					lcAlias = SYS(2015) &&  JUSTSTEM(tcAlias)
					USE (tcAlias) IN 0 ALIAS (lcAlias) SHARED AGAIN
					llOpenedTbl = True
		
				CATCH TO loException
					llError = True
				ENDTRY
				IF llError
					this.RestoreVFPSettings()
					RETURN loReturn
				ENDIF
		
			OTHERWISE
				this.RestoreVFPSettings()
				RETURN loReturn
		ENDCASE
		
		*-*	Create the workbook
		DO CASE
			CASE VARTYPE(tnWB) = "C"
				lnWB = this.CreateWorkbook(tnWB)
				IF lnWB = 0
					lnWB = this.GetWorkbook(tnWB)
				ENDIF
		
			CASE VARTYPE(tnWB) = "N"
				IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
					lnWB = tnWB
				ELSE
					this.RestoreVFPSettings()
					RETURN loReturn
				ENDIF
		
			OTHERWISE
				this.RestoreVFPSettings()
				RETURN loReturn
		ENDCASE
		IF lnWB > 0
			IF tnSheet = 0
				lnSh = this.AddSheet(lnWB, tcSheetName)
			ELSE
				IF this.GetSheetRecord(lnWB, tnSheet)
					lnSh = tnSheet
				ELSE
					RETURN loReturn
				ENDIF
			ENDIF
			IF lnSh > 0
		*-*		Get the record count and display a status window
				RAISEEVENT(this, "OnShowStatusMessage", 3, 0, RECCOUNT(lcAlias))
		
				SELECT &lcAlias
				lnFldCnt = AFIELDS(laFields, lcAlias)
				FOR lnNdx=1 TO lnFldCnt
					TRY
						lcCaption = DBGETPROP(lcAlias + "." + laFields[lnNdx, 1], "FIELD", "CAPTION")
						laFields[lnNdx, 3] = IIF(EMPTY(lcCaption), laFields[lnNdx, 1], lcCaption)
		
					CATCH TO loException
						laFields[lnNdx, 3] = laFields[lnNdx, 1]
					ENDTRY
				ENDFOR
		
		*-*		Place field names in the first row
				lnRow    = tnBegRow
				lnBegCol = tnBegCol
				FOR lnCol=1 TO lnFldCnt
					this.SetCellValue(lnWb, lnSh, lnRow, lnBegCol, laFields[lnCol, 3])
					lnBegCol = lnBegCol + 1
				ENDFOR
		
		*-*		Populate next rows with data
				SELECT &lcAlias
				SCAN
					lnRow = lnRow + 1
					RAISEEVENT(this, "OnShowStatusMessage", 3, lnRow)
					lnBegCol = tnBegCol
					FOR lnCol=1 TO lnFldCnt
						IF laFields[lnCol, 2] = "G"
							this.SetCellValue(lnWb, lnSh, lnRow, lnBegCol, "General")
						ELSE
							this.SetCellValue(lnWb, lnSh, lnRow, lnBegCol, EVALUATE(lcAlias + '.' + laFields[lnCol, 1]))
						ENDIF
						lnBegCol = lnBegCol + 1
					ENDFOR
				ENDSCAN
		
		*-*		Freeze the first row if specified
				IF tlFreeze
					this.FreezePanes(lnWB, lnSh, 1, 0)
				ENDIF
		
				RAISEEVENT(this, "OnShowStatusMessage", 3, -1)
				IF tlSaveWB
					this.SaveWorkbook(lnWB)
				ENDIF
			ELSE
				lnWB = 0
				lnSh = 0
			ENDIF
		ELSE
			lnSh = 0
			lnWB = 0
		ENDIF
		IF llOpenedTbl
			USE IN &lcAlias
		ENDIF
		loReturn.Sheet    = lnSh
		loReturn.Workbook = lnWB
		this.RestoreVFPSettings()
		RETURN loReturn
	ENDPROC

	PROCEDURE SaveTableToWorkbookEx		&& Creates a new workbook from a table; direct write to XLSX file without loading internal cursors
		LPARAMETERS tcAlias, tcXlsxName, taFields, tlFreeze, tcSheetName, tnBegRow, tnBegCol
		LOCAL lcCurAlias, llOpenedTbl, lcAlias, llError, loException, lhFile, lnCol, lcRowDefHt
		LOCAL lnFldCnt, lnRow, lxCellValue, lcTempPath, lcFileName, lcSETCentry, lcCaption, llSuccess, lcSetPoint
		LOCAL lcSeparator
		LOCAL ARRAY laSheetNames[1]
		EXTERNAL ARRAY taFields
		llError = False
		DO CASE
			CASE PCOUNT() < 2
				RETURN False
		
			CASE PCOUNT() = 2
				DIMENSION taFields[1, 2]
				taFields[1, 1] = .NULL.
				tlFreeze       = True
				tcSheetName    = JUSTSTEM(tcAlias)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 3
				tlFreeze    = True
				tcSheetName = JUSTSTEM(tcAlias)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 4
				tcSheetName = JUSTSTEM(tcAlias)
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 5
				tnBegRow = 1
				tnBegCol = 1
		
			CASE PCOUNT() = 6
				tnBegCol = 1
		ENDCASE
		IF VARTYPE(tcSheetName) != 'C' .OR. EMPTY(tcSheetName)
			tcSheetName = JUSTSTEM(tcAlias)
		ENDIF
		tcSheetName = this.CheckSheetName(tcSheetName)
		IF ISNULL(tcSheetName)
			RETURN False
		ENDIF
		
		this.SaveVFPSettings()
		lcCurAlias = ALIAS()
		lcAlias    = JUSTSTEM(tcAlias)
		DO CASE
			CASE USED(lcAlias)
				llOpenedTbl = False
		
			CASE OCCURS("\", tcAlias) > 0
				TRY
					USE (tcAlias) IN 0 ALIAS (lcAlias) SHARED
					llOpenedTbl = True
		
				CATCH TO loException
					llError = True
				ENDTRY
				IF llError
					this.RestoreVFPSettings()
					RETURN False
				ENDIF
		
			OTHERWISE
				this.RestoreVFPSettings()
				RETURN False
		ENDCASE
		
		*-*	Get the table fields to export if not passed as a parameter
		SELECT &lcAlias
		IF ISNULL(taFields) .OR. EMPTY(taFields[1])
			DIMENSION taFields[1, 5]
			taFields = ""
			lnFldCnt = AFIELDS(taFields, lcAlias)
			FOR lnCol=1 TO lnFldCnt
				TRY
					lcCaption = DBGETPROP(lcAlias + "." + taFields[lnCol, 1], "FIELD", "CAPTION")
					taFields[lnCol, 2] = IIF(EMPTY(lcCaption), taFields[lnCol, 1], lcCaption)
		
				CATCH TO loException
					taFields[lnCol, 2] = taFields[lnCol, 1]
				ENDTRY
			ENDFOR
		ELSE	
			lnFldCnt = ALEN(taFields, 1)
			IF ALEN(taFields, 2) < 2 .OR. VARTYPE(taFields[1, 2]) != "C"
				RETURN False
			ENDIF
		ENDIF
		
		*-* Set temporary output path
		lcTempPath = this.CreateWorkingDirectories()
		IF ISNULL(lcTempPath)
			RETURN False
		ENDIF
		
		*-*	Save current settings and then set to US formats
		lcSetPoint  = SET("POINT")
		lcSeparator = SET("SEPARATOR")
		IF lcSetPoint != "."
			SET POINT TO "."
			SET SEPARATOR TO ","
		ENDIF
		
		*-*	Write the workbook
		DIMENSION laSheetNames[1]
		laSheetNames[1] = tcSheetName
		IF this.WriteDirectXMLs(lcTempPath, @laSheetNames, 1)
			TRY
				lcFileName = lcTempPath + "xl\worksheets\sheet1.xml"
				lhFile = FCREATE(lcFileName)
				IF lhFile < 0
					THROW
				ENDIF
			
		*-*		Write sheet XML header information
				FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
				FWRITE(lhFile, '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"')
				FWRITE(lhFile, ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"')
				FWRITE(lhFile, ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"')
				FWRITE(lhFile, ' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
			
		*-*		Write the sheet view information which includes the freeze pane information
				FWRITE(lhFile, '<dimension ref="A1:' + this.ColumnIndexToAscii(lnFldCnt) + TRANSFORM(RECCOUNT(lcAlias)+1) + '"/>')
				IF tlFreeze
					FWRITE(lhFile, '<sheetViews><sheetView tabSelected="1" workbookViewId="0">')
					FWRITE(lhFile, '<pane state="frozenSplit" activePane="bottomLeft" topLeftCell="A2" ySplit="1"/>')
					FWRITE(lhFile, '<selection activeCell="A2" sqref="A2" pane="bottomLeft"/>')
					FWRITE(lhFile, '</sheetView></sheetViews>')
				ELSE
					FWRITE(lhFile, '<sheetViews><sheetView tabSelected="1" workbookViewId="0"/></sheetViews>')
				ENDIF
				FWRITE(lhFile, '<sheetFormatPr defaultRowHeight="15"/>')
			
		*-*		Write sheet cell data values
				FWRITE(lhFile, '<sheetData>')
			
		*-*		Place field names in the first row
				lcRowDefHt = ' ht="30"'
				FWRITE(lhFile, '<row r="' + TRANSFORM(tnBegRow) + '" spans="1:' + TRANSFORM(lnFldCnt) + '"' + lcRowDefHt + '>')
				IF this.DefaultFont = "Calibri" .AND. this.DefaultFontSize = 11
					FOR lnCol=1 TO lnFldCnt
						this.WriteAsInLineString(lhFile, tnBegRow, lnCol+tnBegCol-1, taFields[lnCol, 2])
					ENDFOR
				ELSE
					FOR lnCol=1 TO lnFldCnt
						this.WriteAsInLineFormattedString(lhFile, tnBegRow, lnCol+tnBegCol-1, taFields[lnCol, 2])
					ENDFOR
				ENDIF
				FWRITE(lhFile, '</row>')
			
		*-*		Write the table field values to cells
				lcSETCentry = SET("CENTURY")
				SET CENTURY ON
				lnRow = tnBegRow
				IF this.DefaultFont = "Calibri" .AND. this.DefaultFontSize = 11
					SCAN
						lnRow = lnRow + 1
						FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="1:' + TRANSFORM(lnFldCnt) + '"' + lcRowDefHt + '>')
						FOR lnCol=1 TO lnFldCnt
							lxCellValue = EVALUATE(lcAlias + '.' + taFields[lnCol, 1])
							this.WriteDirectCellValue(lhFile, lnRow, lnCol+tnBegCol-1, lxCellValue)
						ENDFOR
						FWRITE(lhFile, '</row>')
					ENDSCAN
				ELSE
					SCAN
						lnRow = lnRow + 1
						FWRITE(lhFile, '<row r="' + TRANSFORM(lnRow) + '" spans="1:' + TRANSFORM(lnFldCnt) + '"' + lcRowDefHt + '>')
						FOR lnCol=1 TO lnFldCnt
							lxCellValue = EVALUATE(lcAlias + '.' + taFields[lnCol, 1])
							this.WriteDirectCellFormattedValue(lhFile, lnRow, lnCol+tnBegCol-1, lxCellValue)
						ENDFOR
						FWRITE(lhFile, '</row>')
					ENDSCAN
				ENDIF
				SET CENTURY &lcSETCentry
			
		*-*		End the sheet cell data values and worksheet
				FWRITE(lhFile, '</sheetData>')
				FWRITE(lhFile, '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>')
				FWRITE(lhFile, '</worksheet>')
			
			CATCH TO loException
				SET STEP ON
				llError = True
				this.ErrorLevelId = 14
				RAISEEVENT(this, "OnShowErrorMessage", 14, loException.message)
			
			FINALLY
				FCLOSE(lhFile)
				this.SaveAsUTF8(lcFileName)
			ENDTRY
			IF llError
				llSuccess = False
			ELSE
		*-*		Create the XLSX file
				llSuccess = this.CreateExcelFile(lcTempPath, JUSTPATH(tcXlsxName), JUSTSTEM(tcXlsxName))    && Change recommendation by Doug Hennig [placement of code after ELSE]
			ENDIF
			this.RemoveWorkingDirectories(lcTempPath)
		ELSE
			llSuccess = False
		ENDIF
		IF !EMPTY(lcCurAlias)
			SELECT (lcCurAlias)
		ENDIF
		
		*-*	Restore default settings
		this.RestoreVFPSettings()
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE SaveVFPSettings		&& Saves the VFP system settings
		IF !this.VFPSettings.ValuesSet
			this.VFPSettings.ValuesSet  = True
			this.VFPSettings.Alias      = ALIAS()
			this.VFPSettings.Currency   = SET("CURRENCY", 1)     && Bug identified by Doug Hennig
			this.VFPSettings.Exact      = SET("EXACT")
			this.VFPSettings.Safety     = SET("SAFETY")
			this.VFPSettings.SetDeleted = SET("DELETED")
			this.VFPSettings.SetPoint   = SET("POINT")           && Not previously set (bug identified by Doug Hennig)
			this.VFPSettings.Separator  = SET("SEPARATOR")       && Not previously set (bug identified by Doug Hennig)
		
			IF this.VFPSettings.SetPoint != "."
				SET POINT TO "."
			ENDIF
			IF this.VFPSettings.Separator != ","
				SET SEPARATOR TO ","
			ENDIF
		
			SET EXACT OFF
			SET SAFETY OFF
			SET DELETED ON
		ENDIF
	ENDPROC

	PROCEDURE SaveWorkbook		&& Saves the selected workbook as an XLSX file
		LPARAMETERS tnWB
		LOCAL lcTempPath, llReturn, lcSafety, lnTotStages, lcRelsFileName, loException, lcFileName, lcBaseContentType, lcWbXmlPath
		LOCAL lcExtnFileName, lcFileContent, lcBaseType, lnStage, lcExact, lhWBRels, lhCTFile, lnTotStrCnt, lnUnqStrCnt
		LOCAL ARRAY laCount[1]
		llReturn = False
		loException = .NULL.
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			llReturn = True
		
		*-*	Write the XLSX Workbook to XML structures
			lcTempPath = this.CreateWorkingDirectories()
			IF ISNULL(lcTempPath)
				llReturn = False
			ELSE
				this.SaveVFPSettings()
				lcTempPath = ADDBS(lcTempPath)
				TRY
					SELECT COUNT(*) FROM xl_sheets WHERE workbook = tnWB .AND. shdeleted = False INTO ARRAY laCount
					lnStage = 0
					lnTotStages = NVL(laCount[1], 1) + 5
					RAISEEVENT(this, "OnShowStatusMessage", 2, 0, lnTotStages)
		
		*-*			Write the workbook and fixed support XMLs
					lnStage = lnStage + 1
					RAISEEVENT(this, "OnShowStatusMessage", lnStage, lnTotStages)
					IF !this.WriteWorkbookXML(tnWB, lcTempPath)
						THROW
					ENDIF
					IF !this.WriteSupportXMLs(tnWB, lcTempPath)
						THROW
					ENDIF
		
		*-*			Get the number of unique strings assigned and total strings assigned
					SELECT xl_strformat
					SET ORDER TO TAG sortorder
					COUNT FOR workbook = tnWB .AND. !DELETED() TO lnTotStrCnt
				
					SELECT xl_strings
					SET ORDER TO TAG sortorder
					COUNT FOR workbook = tnWB .AND. !DELETED() TO lnUnqStrCnt
					lnTotStrCnt = lnTotStrCnt + lnUnqStrCnt
		
		*-*			If no strings are assigned to cells; remove SharedStrings.xml from relationships
					IF lnTotStrCnt = 0
						DELETE FROM xl_relationships WHERE workbook = tnWB AND reltype = RELS_TYPE_SHAREDSTRINGS
					ENDIF
		
		*-*			Write the contents type xml
					lnStage = lnStage + 1
					RAISEEVENT(this, "OnShowStatusMessage", lnStage, lnTotStages)
					lcFileName = lcTempPath + "[Content_Types].xml"
					lhCTFile  = FCREATE(lcFileName)
					IF lhCTFile < 0
						THROW
					ENDIF
					FWRITE(lhCTFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
					FWRITE(lhCTFile, '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">')
		
					SELECT xl_contenttypes
					SCAN FOR workbook = tnWB .AND. !DELETED()
						FWRITE(lhCTFile, '<Default Extension="' + ALLTRIM(xl_contenttypes.extension) + '" ContentType="' + ALLTRIM(xl_contenttypes.contenttype) + '"/>')
					ENDSCAN
		
					IF xl_workbooks.macroenabled
						FWRITE(lhCTFile, '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.ms-excel.sheet.macroEnabled.main+xml"/>')
					ELSE
						FWRITE(lhCTFile, '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>')
					ENDIF
		
					lcBaseContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.'
					SELECT xl_relationships
					SCAN FOR workbook = tnWB .AND. !DELETED()
						DO CASE
							CASE xl_relationships.reltype = RELS_TYPE_CALCCHAIN
		*						FWRITE(lhCTFile, '<Override PartName="/xl/calcChain.xml"')
		*						FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_DRAWING
								FWRITE(lhCTFile, '<Override PartName="/xl/drawings/drawing' + TRANSFORM(xl_relationships.sheet) + '.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_EXTNLINK
								FWRITE(lhCTFile, '<Override PartName="/xl/externalLinks/externalLink1.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_SHAREDSTRINGS
								FWRITE(lhCTFile, '<Override PartName="/xl/sharedStrings.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_STYLES
								FWRITE(lhCTFile, '<Override PartName="/xl/styles.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_THEME
								FWRITE(lhCTFile, '<Override PartName="/xl/theme/theme1.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>')
		
							CASE xl_relationships.reltype = RELS_TYPE_WORKSHEET
								FWRITE(lhCTFile,  '<Override PartName="/xl/worksheets/sheet' + TRANSFORM(xl_relationships.sheet) + '.xml"')
								FWRITE(lhCTFile, ' ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>')
						ENDCASE
					ENDSCAN
		
					FWRITE(lhCTFile, '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>')
					FWRITE(lhCTFile, '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>')
		
					FWRITE(lhCTFile, '</Types>')
					FCLOSE(lhCTFile)
					this.SaveAsUTF8(lcFileName)
		
		*-*			Write the Workbook Relationship file
					lnStage = lnStage + 1
					RAISEEVENT(this, "OnShowStatusMessage", lnStage, lnTotStages)
					lcRelsFileName = lcTempPath + "xl\_rels\workbook.xml.rels"
					lhWBRels = FCREATE(lcRelsFileName)
					IF lhWBRels < 0
						THROW
					ENDIF
					lcWbXmlPath = lcTempPath + "xl\"
					lcBaseType  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/"
		
					FWRITE(lhWBRels, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
					FWRITE(lhWBRels, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
		
					SELECT xl_relationships
					SCAN FOR workbook = tnWB .AND. relsrc = RELS_SRC_WORKBOOK
						FWRITE(lhWBRels, '<Relationship Id="rId' + TRANSFORM(xl_relationships.relid) + '"')
						FWRITE(lhWBRels, ' Target="' + ALLTRIM(xl_relationships.target) + '"')
						FWRITE(lhWBRels, ' Type="' + lcBaseType + this.GetRelationTypeText(xl_relationships.reltype) + '"/>')
						DO CASE
							CASE xl_relationships.reltype = RELS_TYPE_CALCCHAIN
								lcExtnFileName = lcWbXmlPath + ALLTRIM(xl_relationships.target)
								lcFileContent  = ALLTRIM(xl_relationships.filecontent)
								this.ConvertStringToFile(lcFileContent, lcExtnFileName)
		
							CASE xl_relationships.reltype = RELS_TYPE_EXTNLINK
								lcExtnFileName = lcWbXmlPath + ALLTRIM(xl_relationships.target)
								lcFileContent  = ALLTRIM(xl_relationships.filecontent)
								this.ConvertStringToFile(lcFileContent, lcExtnFileName, 14)
		
								lcExtnFileName = ADDBS(JUSTPATH(lcExtnFileName)) + "_rels\" + JUSTSTEM(lcExtnFileName) + ".xml.rels"
								lcFileContent  = ALLTRIM(xl_relationships.relscontent)
								this.ConvertStringToFile(lcFileContent, lcExtnFileName, 14)
		
							CASE xl_relationships.reltype = RELS_TYPE_THEME
								lcExtnFileName = lcWbXmlPath + ALLTRIM(xl_relationships.target)
								lcFileContent  = ALLTRIM(xl_relationships.filecontent)
								this.ConvertStringToFile(lcFileContent, lcExtnFileName)
		
							CASE xl_relationships.reltype = RELS_TYPE_VBAPROJECT
								lcExtnFileName = lcWbXmlPath + ALLTRIM(xl_relationships.target)
								lcFileContent  = ALLTRIM(xl_relationships.filecontent)
								this.ConvertStringToFile(lcFileContent, lcExtnFileName, 14)
		
							CASE xl_relationships.reltype = RELS_TYPE_WORKSHEET
								IF SEEK(BINTOC(tnWB)+BINTOC(xl_relationships.sheet), "xl_sheets", "sheet")
									lnStage = lnStage + 1
									RAISEEVENT(this, "OnShowStatusMessage", lnStage, lnTotStages)
									IF !this.WriteSheetXMLs(tnWB, xl_sheets.sheet, lcTempPath)
										THROW
									ENDIF
								ELSE
									THROW
								ENDIF
		
							CASE xl_relationships.reltype = RELS_TYPE_SHAREDSTRINGS
								IF !this.WriteStringsXML(tnWB, lcTempPath, lnTotStrCnt, lnUnqStrCnt)
									THROW
								ENDIF
		
							CASE xl_relationships.reltype = RELS_TYPE_STYLES
								IF !this.WriteStylesXML(tnWB, lcTempPath)
									THROW
								ENDIF
		
							CASE xl_relationships.reltype = RELS_TYPE_UNKNOWN
		
						ENDCASE
					ENDSCAN
					FWRITE(lhWBRels, '</Relationships>')
					FCLOSE(lhWBRels)
					this.SaveAsUTF8(lcRelsFileName)
		
					llReturn = this.CreateExcelFile(lcTempPath, xl_workbooks.filepath, xl_workbooks.wbname, xl_workbooks.macroenabled)
		
				CATCH TO loException
					llReturn = False
					SET STEP ON
				ENDTRY
				this.RemoveWorkingDirectories(lcTempPath)
				this.RestoreVFPSettings()
				RAISEEVENT(this, "OnShowStatusMessage", 2, -1)
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SaveWorkbookAs		&& Saves the selected workbook with the new name
		LPARAMETERS tnWB, tcWBName
		LOCAL lcName, lcPath, llReturn
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook") .AND. !EMPTY(tcWBName)
			lcName = JUSTSTEM(tcWBName)
			lcPath = JUSTPATH(tcWBName)
			IF EMPTY(lcPath)
				lcPath = SYS(5) + SYS(2003)
			ENDIF
			REPLACE xl_workbooks.wbname   WITH lcName, ;
					xl_workbooks.filepath WITH lcPath IN xl_workbooks
			llReturn = this.SaveWorkbook(tnWB)
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellAlignment		&& Sets the cell horizontal and vertical alignments
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tcHorizAlign, tcVertAlign
		LOCAL llReturn, lnCellXfsId
		DO CASE
			CASE this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
				RETURN False
			
			CASE PCOUNT() < 4
				RETURN False
		ENDCASE
		IF this.GetSheetRecord(tnWB, tnSheet)
			this.SaveVFPSettings()
			DO CASE
				CASE VARTYPE(tcHorizAlign) = 'C' .AND. VARTYPE(tcVertAlign) = 'C'
					IF INLIST(tcHorizAlign, CELL_HORIZ_ALIGN_LEFT, CELL_HORIZ_ALIGN_RIGHT, CELL_HORIZ_ALIGN_CENTER, CELL_HORIZ_ALIGN_DISTRIB, CELL_HORIZ_ALIGN_CTR_CONTIN, CELL_HORIZ_ALIGN_JUSTIFIED, CELL_HORIZ_ALIGN_GENERAL) .AND. ;
					   INLIST(tcVertAlign, CELL_VERT_ALIGN_TOP, CELL_VERT_ALIGN_BOTTOM, CELL_VERT_ALIGN_CENTER, CELL_VERT_ALIGN_DISTRIB, CELL_VERT_ALIGN_JUSTIFIED)
		
						IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
							IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
								lnCellXfsId = xl_cellxfs.id
								SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
								IF _TALLY > 1
									lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, xl_cellxfs.fillId, xl_cellxfs.borderId, tcHorizAlign, tcVertAlign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
								ELSE
									REPLACE xl_cellxfs.halign WITH tcHorizAlign, ;
											xl_cellxfs.valign WITH tcVertAlign IN xl_cellxfs
								ENDIF
								USE IN SELECT('xl_temp')
							ELSE
								lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, tcHorizAlign, tcVertAlign, 0, 0, 0)
							ENDIF
							REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
						ELSE
							lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, tcHorizAlign, tcVertAlign, 0, 0, 0)
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted) ;
								VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False)
							this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
						ENDIF
						llReturn = True
					ELSE
						llReturn = False
					ENDIF
		
				CASE VARTYPE(tcHorizAlign) = 'C' .AND. VARTYPE(tcVertAlign) != 'C'
					IF INLIST(tcHorizAlign, CELL_HORIZ_ALIGN_LEFT, CELL_HORIZ_ALIGN_RIGHT, CELL_HORIZ_ALIGN_CENTER, CELL_HORIZ_ALIGN_DISTRIB, CELL_HORIZ_ALIGN_CTR_CONTIN, CELL_HORIZ_ALIGN_JUSTIFIED, CELL_HORIZ_ALIGN_GENERAL)
						IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
							IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
								lnCellXfsId = xl_cellxfs.id
								SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
								IF _TALLY > 1
									lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, xl_cellxfs.fillId, xl_cellxfs.borderId, tcHorizAlign, "", xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
								ELSE
									REPLACE xl_cellxfs.halign WITH tcHorizAlign, ;
											xl_cellxfs.valign WITH "" IN xl_cellxfs
								ENDIF
								USE IN SELECT('xl_temp')
							ELSE
								lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, tcHorizAlign, "", 0, 0, 0)
							ENDIF
							REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
						ELSE
							lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, tcHorizAlign, "", 0, 0, 0)
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted) ;
								VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False)
							this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
						ENDIF
						llReturn = True
					ELSE
						llReturn = False
					ENDIF
		
				CASE VARTYPE(tcHorizAlign) != 'C' .AND. VARTYPE(tcVertAlign) = 'C'
					IF INLIST(tcVertAlign, CELL_VERT_ALIGN_TOP, CELL_VERT_ALIGN_BOTTOM, CELL_VERT_ALIGN_CENTER, CELL_VERT_ALIGN_DISTRIB, CELL_VERT_ALIGN_JUSTIFIED)
						IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
							IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
								lnCellXfsId = xl_cellxfs.id
								SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
								IF _TALLY > 1
									lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, xl_cellxfs.fillId, xl_cellxfs.borderId, "", tcVertAlign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
								ELSE
									REPLACE xl_cellxfs.halign WITH "", ;
											xl_cellxfs.valign WITH tcVertAlign IN xl_cellxfs
								ENDIF
								USE IN SELECT('xl_temp')
							ELSE
								lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", tcVertAlign, 0, 0, 0)
							ENDIF
							REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
						ELSE
							lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", tcVertAlign, 0, 0, 0)
							INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted) ;
								VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False)
							this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
						ENDIF
						llReturn = True
					ELSE
						llReturn = False
					ENDIF
		
				OTHERWISE
					IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
						REPLACE xl_cells.halign WITH "", ;
								xl_cells.valign WITH "" IN xl_cells
					ENDIF
					llReturn = True
			ENDCASE
			this.RestoreVFPSettings()
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellAlignmentRange		&& Sets the cell alignment for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tcHorizAlign, tcVertAlign
		LOCAL lnRow, lnCol, llReturn
		IF PCOUNT() < 7 .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellAlignment(tnWB, tnSheet, lnRow, lnCol, tcHorizAlign, tcVertAlign)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellBorder
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnBorders, tcStyle, tnColor
		LOCAL lcLStyle, lnLColor, lcRStyle, lcTStyle, lnTColor, lcBStyle, lnBColor, lcDStyle, lnDColor, lnDiagDn, lnDiagUp
		LOCAL lnRColor, llReturn, lcAlias
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF VARTYPE(tnBorders) != 'N' .OR. EMPTY(tnBorders) .OR. !BETWEEN(tnBorders, 0, 63)
				tnBorders = 0
			ENDIF
			IF !this.IsValidBorderStyle(tcStyle)
				tcStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnColor) != 'N' .OR. !BETWEEN(tnColor, 0, RGB(255,255,255)) .OR. EMPTY(tnColor)
				tnColor = RGB(0, 0, 0)
			ENDIF
			STORE 0 TO lnDiagDn, lnDiagUp
			STORE RGB(0,0,0) TO lnLColor, lnRColor, lnTColor, lnBColor, lnDColor
			STORE BORDER_STYLE_NONE TO lcLStyle, lcRStyle, lcTStyle, lcBStyle, lcDStyle
			IF BITTEST(tnBorders, 0)
				lcLStyle = tcStyle
				lnLColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 1)
				lcRStyle = tcStyle
				lnRColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 2)
				lcTStyle = tcStyle
				lnTColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 3)
				lcBStyle = tcStyle
				lnBColor = tnColor
			ENDIF
			IF BITTEST(tnBorders, 4)
				lcDStyle = tcStyle
				lnDColor = tnColor
				lnDiagDn = 1
			ENDIF
			IF BITTEST(tnBorders, 5)
				lcDStyle = tcStyle
				lnDColor = tnColor
				lnDiagUp = 1
			ENDIF
			this.SetCellBorderEx(tnWB, tnSheet, tnCellRow, tnCellCol, lcLStyle, lnLColor, lcRStyle, lnRColor, ;
								 lcTStyle, lnTColor, lcBStyle, lnBColor, lcDStyle, lnDColor, lnDiagDn, lnDiagUp)
			this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellBorderEx
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp
		LOCAL lnCellXfsId, lnBorderId
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF !this.IsValidBorderStyle(tcLStyle)
				tcLStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnLColor) != 'N' .OR. EMPTY(tnLColor) .OR. !BETWEEN(tnLColor, 0, RGB(255,255,255))
				tnLColor = RGB(0, 0, 0)
			ENDIF
		
			IF !this.IsValidBorderStyle(tcRStyle)
				tcRStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnRColor) != 'N' .OR. EMPTY(tnRColor) .OR. !BETWEEN(tnRColor, 0, RGB(255,255,255))
				tnRColor = RGB(0, 0, 0)
			ENDIF
		
			IF !this.IsValidBorderStyle(tcTStyle)
				tcTStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnTColor) != 'N' .OR. EMPTY(tnTColor) .OR. !BETWEEN(tnTColor, 0, RGB(255,255,255))
				tnTColor = RGB(0, 0, 0)
			ENDIF
		
			IF !this.IsValidBorderStyle(tcBStyle)
				tcBStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnBColor) != 'N' .OR. EMPTY(tnBColor) .OR. !BETWEEN(tnBColor, 0, RGB(255,255,255))
				tnBColor = RGB(0, 0, 0)
			ENDIF
		
			IF !this.IsValidBorderStyle(tcDStyle)
				tcDStyle = BORDER_STYLE_NONE
			ENDIF
			IF VARTYPE(tnDColor) != 'N' .OR. EMPTY(tnDColor) .OR. !BETWEEN(tnDColor, 0, RGB(255,255,255))
				tnDColor = RGB(0, 0, 0)
			ENDIF
			IF VARTYPE(tnDiagDn) != 'N' .OR. !BETWEEN(tnDiagDn, 0, 3)
				tnDiagDn = 0
			ENDIF
			IF VARTYPE(tnDiagUp) != 'N' .OR. !BETWEEN(tnDiagUp, 0, 3)
				tnDiagUp = 0
			ENDIF
		
		*-*	Get the border record Id
			IF this.GetBordersRecord(tnWB, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp)
				lnBorderId = xl_borders.id
			ELSE
				lnBorderId = this.GetNextId(tnWB, 0, 'xl_borders')
				INSERT INTO xl_borders (workbook, id, lstyle, lcolor, rstyle, rcolor, tstyle, tcolor, bstyle, bcolor, dstyle, dcolor, diagdn, diagup) ;
					VALUES (tnWB, lnBorderId, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp)
			ENDIF
		*-*	Set the XfsId to the cell record
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, xl_cellxfs.fillId, lnBorderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
					ELSE
						REPLACE xl_cellxfs.borderId WITH lnBorderId IN xl_cellxfs
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, lnBorderId, "", "", 0, 0, 0)
				ENDIF
				REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, lnBorderId, "", "", 0, 0, 0)
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellBorderRange		&& Sets the cell border for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tnBorder, tcStyle, tnColor
		LOCAL llReturn, lnRow, lnCol
		IF PCOUNT() < 7 .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellBorder(tnWB, tnSheet, lnRow, lnCol, tnBorder, tcStyle, tnColor)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellFill		&& Sets the fill color for the cell
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnFColor, tnBColor, tcPatternType
		LOCAL llReturn, lnFillId, lnCellXfsId, lnFIndex, lnBIndex
		DO CASE
			CASE PCOUNT() < 5
				RETURN False
			
			CASE PCOUNT() = 5
				tnBColor = RGB(255,255,255)
				tcPatternType = FILL_STYLE_SOLID
		
			CASE PCOUNT() = 6
				tcPatternType = FILL_STYLE_SOLID
		ENDCASE
		IF this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF PCOUNT() = 5 .OR. PCOUNT() = 6 .OR. VARTYPE(tcPatternType) != "C" .OR. EMPTY(tcPatternType)
			tcPatternType = FILL_STYLE_SOLID
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF VARTYPE(tnBColor) != 'N'
				tnBColor = RGB(255,255,255)
			ENDIF
			IF VARTYPE(tnFColor) = 'N' .AND. BETWEEN(tnFColor, 0, RGB(255, 255, 255))
				tnFColor = FLOOR(tnFColor)
				lnFIndex = 0
			ELSE
				tnFColor = 0
				lnFIndex = 64
			ENDIF
			IF VARTYPE(tnBColor) = 'N' .AND. BETWEEN(tnBColor, 0, RGB(255, 255, 255))
				tnBColor = FLOOR(tnBColor)
				lnBIndex = 0
			ELSE
				tnBColor = RGB(0, 0, 0)
				lnBIndex = 64
			ENDIF
		*-*	Get the fill record id
			IF SEEK(BINTOC(tnWB)+BINTOC(tnFColor)+BINTOC(tnBColor)+tcPatternType, "xl_fills", "fillcolor")
				lnFillId = xl_fills.id
				REPLACE xl_fills.theme     WITH 0, ;
						xl_fills.tint      WITH 0, ;
						xl_fills.fgindexed WITH lnFIndex, ;
						xl_fills.bgindexed WITH lnBIndex IN xl_fills
			ELSE
				lnFillId = this.GetNextId(tnWB, 0, 'xl_fills')
				INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
					VALUES (tnWB, lnFillId, tnFColor, tnBColor, tcPatternType, 0, 0, lnFIndex, lnBIndex, -1, -1)
			ENDIF
		*-*	Set the XfsId to the cell record
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, lnFillId, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
					ELSE
						REPLACE xl_cellxfs.fillId WITH lnFillId IN xl_cellxfs
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, lnFillId, 0, "", "", 0, 0, 0)
				ENDIF
				REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, lnFillId, 0, "", "", 0, 0, 0)
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellFillRange		&& Sets the fill for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tnFColor, tnBColor, tcPatternType
		LOCAL lnRow, lnCol, llReturn
		DO CASE
			CASE PCOUNT() < 7
				RETURN False
		
			CASE PCOUNT() = 7
				tnBColor = RGB(255,255,255)
				tcPatternType = FILL_STYLE_SOLID
		
			CASE PCOUNT() = 8
				tcPatternType = FILL_STYLE_SOLID
		ENDCASE
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF VARTYPE(tnBColor) != 'N'
			tnBColor = RGB(255,255,255)
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellFill(tnWB, tnSheet, lnRow, lnCol, tnFColor, tnBColor, tcPatternType)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellFont		&& Sets the selected cell font format
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos
		LOCAL llReturn
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF VARTYPE(tcFName) != 'C' .OR. EMPTY(tcFName)
				tcFName = 'Calibri'
			ENDIF
			tcFName = PROPER(ALLTRIM(tcFName))
			IF VARTYPE(tnFSize) != 'N' .OR. EMPTY(tnFSize) .OR. tnFSize < 0.5
				tnFSize = 11
			ELSE
				tnFSize = INT(tnFSize)
			ENDIF
			IF VARTYPE(tlBold) != 'L'
				tlBold = False
			ENDIF
			IF VARTYPE(tlItalic) != 'L'
				tlItalic = False
			ENDIF
			IF VARTYPE(tnFColor) = 'N' .AND. BETWEEN(tnFColor, 0, RGB(255, 255, 255))
				tnFColor = FLOOR(tnFColor)
			ELSE
				tnFColor = RGB(0, 0, 0)
			ENDIF
			IF VARTYPE(tlStrikThr) != 'L'
				tlStrikThr = False
			ENDIF
			IF VARTYPE(tcULine) != 'C' .OR. !INLIST(tcULine, UNDERLINE_SINGLE, UNDERLINE_DOUBLE, UNDERLINE_SINGLEACCOUNTING, UNDERLINE_DOUBLEACCOUNTING, UNDERLINE_NONE)
				tcULine = UNDERLINE_NONE
			ENDIF
			IF VARTYPE(tcVPos) != 'C' .OR. !INLIST(tcVPos, FONT_VERTICAL_BASELINE, FONT_VERTICAL_SUBSCRIPT, FONT_VERTICAL_SUPERSCRIPT)
				tcVPos = FONT_VERTICAL_BASELINE
			ENDIF
		*-*	Get the font record id
			IF SEEK(BINTOC(tnWB)+PADR(tcFName, 100) + STR(tnFSize, 5, 1) + TRANSFORM(tlBold) + TRANSFORM(tlItalic) + PADL(tnFColor, 15) + PADR(tcULine, 16) + ;
					TRANSFORM(tlStrikThr) + PADR(tcVPos, 11), "xl_fonts", "cellformat")
		
				lnFontId = xl_fonts.id
				REPLACE xl_fonts.theme   WITH 0, ;
						xl_fonts.tint    WITH 0, ;
						xl_fonts.indexed WITH 0 IN xl_fonts
			ELSE
				lnFontId = this.GetNextId(tnWB, 0, 'xl_fonts')
				INSERT INTO xl_fonts (workbook, id, fname, fsize, fbold, fitalic, fcolor, uline, strkthr, fvpos) ;
					VALUES (tnWB, lnFontId, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos)
			ENDIF
		*-*	Set the XfsId to the cell record
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, lnFontId, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
					ELSE
						REPLACE xl_cellxfs.fontid WITH lnFontId IN xl_cellxfs
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					lnCellXfsId = this.GetCellXfsId(tnWB, 0, lnFontId, 0, 0, "", "", 0, 0, 0)
				ENDIF
				REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, lnFontId, 0, 0, "", "", 0, 0, 0)
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellFontRange		&& Sets the cell font format for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos
		LOCAL lnRow, lnCol, llReturn
		IF PCOUNT() < 7 .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellFont(tnWB, tnSheet, lnRow, lnCol, tcFName, tnFSize, tlBold, tlItalic, tnFColor, tcULine, tlStrikThr, tcVPos)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellFormula		&& Sets the cell formula expression
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tcCellFormula
		LOCAL llReturn
		IF PCOUNT() < 5 .OR. VARTYPE(tcCellFormula) != "C" .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				REPLACE xl_cells.cellvalue   WITH "", ;
						xl_cells.stringid    WITH 0, ;
						xl_cells.datatype    WITH DATA_TYPE_FORMULA, ;
						xl_cells.formulatype WITH "", ;
						xl_cells.formulasi   WITH -1, ;
						xl_cells.celldeleted WITH False IN xl_cells
			ELSE
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, formulatype, formulasi, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_FORMULA, "", -1, 0, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnCellRow)+BINTOC(tnCellCol), "xl_formulas", "cell")
				REPLACE xl_formulas.formulatype WITH "", ;
						xl_formulas.si          WITH -1, ;
						xl_formulas.ref         WITH "", ;
						xl_formulas.formrel     WITH "", ;
						xl_formulas.cellformula WITH tcCellFormula IN xl_formulas
			ELSE
				INSERT INTO xl_formulas (workbook, sheet, cellrow, cellcol, formulatype, si, ref, formrel, cellformula) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, "", -1, "", "", tcCellFormula)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellIndent		&& Sets the cell indentation value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnIndent
		LOCAL lnCellXfsId
		IF PCOUNT() != 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF VARTYPE(tnIndent) != 'N'
			RETURN False
		ENDIF
		tnIndent = FLOOR(tnIndent)
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
				lnCellXfsId = xl_cellxfs.id
				SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
				IF _TALLY > 1
					lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontid, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, tnIndent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
				ELSE
					REPLACE xl_cellxfs.indent WITH tnIndent IN xl_cellxfs
				ENDIF
				USE IN SELECT('xl_temp')
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", tnIndent, 0, 0)
			ENDIF
			REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
		ELSE
			lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", tnIndent, 0, 0)
			INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
				VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
			this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetCellInLineFormatText		&& Saves the in-line format definition to a cell value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, toInLine
		LOCAL lnNdx, lcRText, llFBold, llFItalic, lnFColor, lcFName, lnFSize, lcULine, llStrkThr, lcFVPos
		IF this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF tnWB = toInLine.Workbook
			IF ISNULL(toInLine.StringId)
				toInLine.StringId = this.AddStringValue(toInLine.Workbook, toInLine.StringValue, True)
			ENDIF
			IF this.GetCellRecord(toInLine.Workbook, tnSheet, tnCellRow, tnCellCol)
				REPLACE xl_cells.cellvalue   WITH "StringValue", ;
						xl_cells.cellxfs     WITH 0, ;
						xl_cells.stringid    WITH toInLine.StringId, ;
						xl_cells.datatype    WITH DATA_TYPE_CHAR, ;
						xl_cells.celldeleted WITH False IN xl_cells
			ELSE
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, cellvalue, stringid, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (toInLine.Workbook, tnSheet, tnCellRow, tnCellCol, "StringValue", toInLine.StringId, DATA_TYPE_CHAR, 0, False, -1, 0)
		
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			FOR lnNdx=1 TO toInLine.Count
				lcRText   = SUBSTR(toInLine.StringValue, toInLine.Characters[lnNdx].BegPos, toInLine.Characters[lnNdx].Length)
				llFBold   = toInLine.Characters[lnNdx].FontBold
				llFItalic = toInLine.Characters[lnNdx].FontItalic
				lnFColor  = toInLine.Characters[lnNdx].FontColor
				lcFName   = toInLine.Characters[lnNdx].FontName
				lnFSize   = toInLine.Characters[lnNdx].FontSize
				lcULine   = toInLine.Characters[lnNdx].Underline
				llStrkThr = toInLine.Characters[lnNdx].StrikeThru
				lcFVPos   = ICASE(toInLine.Characters[lnNdx].SubScript, FONT_VERTICAL_SUBSCRIPT, toInLine.Characters[lnNdx].SuperScript, FONT_VERTICAL_SUPERSCRIPT, FONT_VERTICAL_BASELINE)
		
				INSERT INTO xl_strformat (workbook, id, index, stringxml, stringval, fbold, fitalic, fcolor, fname, fsize, uline, strkthr, fvpos, theme, tint, indexed) ;
					VALUES (toInLine.Workbook, toInLine.StringId, lnNdx, this.GetXMLString(lcRText), lcRText, llFBold, llFItalic, lnFColor, lcFName, lnFSize, lcULine, ;
							llStrkThr, lcFVPos, 0, 0, 0)
			ENDFOR
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE SetCellNumberDecimals		&& Sets the number of decimals to display
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnNumDecimals
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF VARTYPE(tnNumDecimals) != 'N' .OR. tnNumDecimals < 0
			RETURN False
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			REPLACE xl_cells.numdec WITH tnNumDecimals IN xl_cells
		ELSE
			INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, nbrfmtid, numdec, celldeleted) ;
				VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, 0, tnNumDecimals, False)
			this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetCellNumberFormat		&& Sets the cell number format
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnNumFmtId
		LOCAL llReturn, lnNumFmtId, loException, lcFormatCode
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF VARTYPE(tnNumFmtId) != 'N'
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			DO CASE
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYYHMM
					lcFormatCode = CHRTRAN("[$-409]m|d|yy h:mm", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_DDMMMYYYY_TTAM
					lcFormatCode = CHRTRAN("[$-409]dd|mmm|yyyy h:mm AM/PM;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_DDMMMYYYY_TT24
					lcFormatCode = CHRTRAN("[$-409]dd|mmm|yyyy h:mm;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MMMDDYYYY_TTAM
					lnNumFmtId = this.AddCustomDateTimeFormat(tnWB, '[$-409]mmm d, yyyy h:mm AM/PM;@')
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MMMDDYYYY_TT24
					lnNumFmtId = this.AddCustomDateTimeFormat(tnWB, '[$-409]mmm d, yyyy hh:mm;@')
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYY_TTAM
					lcFormatCode = CHRTRAN("[$-409]m|d|yy h:mm AM/PM;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId = CELL_FORMAT_DATETIME_MDYY_TT24
					lcFormatCode = CHRTRAN("[$-409]m|d|yy hh:mm;@", "|", this.DateTimeSeparator)
					lnNumFmtId   = this.AddCustomDateTimeFormat(tnWB, lcFormatCode)
		
				CASE tnNumFmtId < START_NUMERIC_FORMAT_ID
					lnNumFmtId = tnNumFmtId
		
				CASE tnNumFmtId >= START_NUMERIC_FORMAT_ID
					IF SEEK(BINTOC(tnWB)+BINTOC(tnNumFmtId), "xl_numfmts", "id")
						lnNumFmtId = xl_numfmts.id
					ELSE
						IF !this.AddClassDefinedNumericFormats(tnWB, tnNumFmtId)
							RETURN False
						ENDIF
						lnNumFmtId = tnNumFmtId
					ENDIF
		
				OTHERWISE
					RETURN False
			ENDCASE
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, xl_cellxfs.fontid, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
					ELSE
						TRY
							REPLACE xl_cellxfs.numFmtId WITH lnNumFmtId IN xl_cellxfs
						CATCH TO loException
							SET STEP ON
						ENDTRY
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, 0, 0, 0, "", "", 0, 0, 0)
				ENDIF
				REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, 0, 0, 0, "", "", 0, 0, 0)
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellNumberFormatRange		&& Sets the number format for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tnNumFormat
		LOCAL lnRow, lnCol, llReturn
		IF PCOUNT() < 7 .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellNumberFormat(tnWB, tnSheet, lnRow, lnCol, tnNumFormat)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellStyle		&& Sets the cell style
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnCellXfsId
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				REPLACE xl_cells.cellxfs WITH tnCellXfsId IN xl_cells
			ELSE
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, tnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE SetCellStyleRange		&& Sets the cell style for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tnCellXfsId
		LOCAL lnRow, lnCol
		IF PCOUNT() < 7 .OR. tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(tnCellXfsId), "xl_cellxfs", "id")
			FOR lnRow=tnBegRow TO tnEndRow
				FOR lnCol=tnBegCol TO tnEndCol
					IF this.GetCellRecord(tnWB, tnSheet, lnRow, lnCol)
						REPLACE xl_cells.cellxfs WITH tnCellXfsId IN xl_cells
					ELSE
						INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
							VALUES (tnWB, tnSheet, lnRow, lnCol, DATA_TYPE_NONE, tnCellXfsId, False, -1, 0)
						this.SetRowMaxColumn(tnWB, tnSheet, lnRow, lnCol)
					ENDIF
				ENDFOR
			ENDFOR
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE SetCellTextRotation		&& Sets the cell text rotation value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnRotation
		LOCAL lnCellXfsId, lcAlias
		IF PCOUNT() != 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF VARTYPE(tnRotation) != 'N'
			RETURN False
		ENDIF
		IF !BETWEEN(tnRotation, -90, 90)
			RETURN False
		ENDIF
		tnRotation = FLOOR(tnRotation)
		IF tnRotation < 0
			tnRotation = 90 - tnRotation
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
				lnCellXfsId = xl_cellxfs.id
				SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
				IF _TALLY > 1
					lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontid, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, tnRotation)
				ELSE
					REPLACE xl_cellxfs.rotation WITH tnRotation IN xl_cellxfs
				ENDIF
				USE IN SELECT('xl_temp')
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", 0, 0, tnRotation)
			ENDIF
			REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
		ELSE
			lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", 0, 0, tnRotation)
			INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
				VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
			this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetCellValidation		&& Adds a cell validation
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tnType, tnStyle, tnOperator, tlAllowBlank, tlShowInputMsg, tlShowErrMsg, tcErrMsg, tcErrTitle, tcPrompt, tcFormula1, tcFormula2
		LOCAL lcAlias, lnIndex, llFormula
		lcAlias = ALIAS()
		IF PCOUNT() < 14 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF !BETWEEN(tnType, 0, CUSTOM_VALID_TYPE)
			RETURN False
		ENDIF
		IF VARTYPE(tcFormula1) != "C"
			tcFormula1 = TRANSFORM(tcFormula1)
		ENDIF
		IF EMPTY(tcFormula1) .OR. LEN(tcFormula1) > 254
			RETURN False
		ENDIF
		IF LEFT(tcFormula1, 1) = "="
			llFormula  = True
			tcFormula1 = ALLTRIM(SUBSTR(tcFormula1, 2))
		ELSE
			llFormula  = False
		ENDIF
		IF PCOUNT() < 15
			tcFormula2 = ""
		ENDIF
		IF VARTYPE(tcFormula2) != "C"
			tcFormula2 = TRANSFORM(tcFormula2)
		ENDIF
		IF LEN(tcFormula2) > 254
			RETURN False
		ENDIF
		IF LEFT(tcFormula2, 1) = "="
			tcFormula2 = ALLTRIM(SUBSTR(tcFormula2, 2))
		ENDIF
		IF VARTYPE(tnStyle) = "N"
			IF !BETWEEN(tnStyle, 0, INFO_VALID_STYLE)
				RETURN False
			ENDIF
		ELSE
			tnStyle = 0
		ENDIF
		IF VARTYPE(tnOperator) = "N"
			IF !BETWEEN(tnOperator, 0, GREATOREQUAL_VALID_OPER)
				RETURN False
			ENDIF
		ELSE
			tnOperator = 0
		ENDIF
		IF VARTYPE(tcErrMsg) != "C"
			tcErrMsg = ""
		ENDIF
		IF VARTYPE(tcErrTitle) != "C"
			tcErrTitle = ""
		ENDIF
		IF VARTYPE(tcPrompt) != "C"
			tcPrompt = ""
		ENDIF
		IF VARTYPE(tlAllowBlank) != "L"
			tlAllowBlank = True
		ENDIF
		IF VARTYPE(tlShowInputMsg) != "L"
			tlShowInputMsg = True
		ENDIF
		IF VARTYPE(tlShowErrMsg) != "L"
			tlShowErrMsg = True
		ENDIF
		*-*	Check if the cell is part of a merged range of cells; if so set the cell to the upper left cell of the merged range
		IF RECCOUNT('xl_mergecells') > 0
			SELECT xl_mergecells
			LOCATE FOR workbook = tnWB .AND. sheet = tnSheet .AND. BETWEEN(tnCellRow, begrow, endrow) .AND. BETWEEN(tnCellCol, begcol, endcol) .AND. !DELETED()
			IF FOUND()
				tnCellRow = xl_mergecells.begrow
				tnCellCol = xl_mergecells.begcol
			ENDIF
		ENDIF
		SELECT xl_validation
		LOCATE FOR BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) ;
			.AND. UPPER(formula1) = UPPER(PADR(tcFormula1, 240)) ;
			.AND. UPPER(formula2) = UPPER(PADR(tcFormula2, 240)) .AND. !DELETED()
		IF FOUND()
			lnIndex = 0
			DO WHILE FOUND()
				IF xl_validation.vtype = tnType ;
				  .AND. xl_validation.vstyle = tnStyle ;
				  .AND. xl_validation.voperator = tnOperator ;
				  .AND. xl_validation.errmsg = tcErrMsg ;
				  .AND. xl_validation.errtitle = tcErrTitle ;
				  .AND. xl_validation.allowblank = tlAllowBlank ;
				  .AND. xl_validation.showinpmsg = tlShowInputMsg ;
				  .AND. xl_validation.showerrmsg = tlShowErrMsg ;
				  .AND. xl_validation.vprompt = tcPrompt
		
					lnIndex = xl_validation.validndx
					EXIT
				ENDIF
				CONTINUE
			ENDDO
			IF lnIndex = 0
				INSERT INTO xl_validation (workbook, sheet, vtype, vstyle, voperator, errmsg, errtitle, vprompt, allowblank, showinpmsg, showerrmsg, formula, formula1, formula2) ;
					VALUES (tnWB, tnSheet, tnType, tnStyle, tnOperator, tcErrMsg, tcErrTitle, tcPrompt, tlAllowBlank, tlShowInputMsg, tlShowErrMsg, llFormula, tcFormula1, tcFormula2)
				lnIndex = xl_validation.validndx
			ENDIF
		ELSE
			INSERT INTO xl_validation (workbook, sheet, vtype, vstyle, voperator, errmsg, errtitle, vprompt, allowblank, showinpmsg, showerrmsg, formula, formula1, formula2) ;
				VALUES (tnWB, tnSheet, tnType, tnStyle, tnOperator, tcErrMsg, tcErrTitle, tcPrompt, tlAllowBlank, tlShowInputMsg, tlShowErrMsg, llFormula, tcFormula1, tcFormula2)
			lnIndex = xl_validation.validndx
		ENDIF
		IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
			REPLACE xl_cells.validndx WITH lnIndex IN xl_cells
		ELSE
			INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
				VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, 0, False, -1, lnIndex)
			this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
		ENDIF
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetCellValue		&& Sets the cell value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, txCellValue, tlAppend
		LOCAL lcCellValue, lcDataType, lnStringId, lnSetDecimals, lnWholePart, lnFractPart, lnNumFmtId, lcHorzAlign
		LOCAL llReturn
		IF PCOUNT() < 5 .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF PCOUNT() = 5
			tlAppend = False
		ENDIF
		llReturn = True
		TRY
			this.SaveVFPSettings()
		*-*	Check if the cell is part of a merged range of cells; if so set the cell to the upper left cell of the merged range
			IF RECCOUNT('xl_mergecells') > 0
				SELECT xl_mergecells
				LOCATE FOR workbook = tnWB .AND. sheet = tnSheet .AND. tnCellRow >= begrow .AND. tnCellRow <= endrow .AND. tnCellCol >= begcol .AND. tnCellCol <= endcol .AND. !DELETED()
				IF FOUND()
					tnCellRow = xl_mergecells.begrow
					tnCellCol = xl_mergecells.begcol
				ENDIF
			ENDIF
		*-*	Assign the data type based on type of value and add to cell
			lnStringId  = -1
			lcHorzAlign = ""
			lnNumFmtId  = 0
			lcDataType  = VARTYPE(txCellValue)
			IF ISNULL(txCellValue)
				txCellValue = this.NullValue
			ENDIF
			DO CASE
				CASE ISNULL(txCellValue)
					THROW
			
				CASE lcDataType = DATA_TYPE_LOGICAL
					lcCellValue = IIF(txCellValue, GETWORDNUM(this.TrueFalseValue, 1, "|"), GETWORDNUM(this.TrueFalseValue, 2, "|"))
			
				CASE lcDataType = DATA_TYPE_CHAR
					txCellValue = ALLTRIM(txCellValue)
					IF tlAppend
						lcCellValue = this.GetCellValue(tnWB, tnSheet, tnCellRow, tnCellCol)
						IF EMPTY(lcCellValue) .OR. ISNULL(lcCellValue)
							lcCellValue = txCellValue
						ELSE
							lcCellValue = lcCellValue + CR + txCellValue
						ENDIF
					ELSE
						lcCellValue = txCellValue
					ENDIF
					IF LEN(lcCellValue) > LIMITS_MAX_CELL_CHARS
						THROW
					ENDIF
			
				CASE lcDataType = DATA_TYPE_DATE
					IF txCellValue >= DATE(1900, 1, 1)
						lcCellValue = TRANSFORM(INT(txCellValue - DATE(1900, 1, 1) + 2))
					ELSE
						lcDataType  = DATA_TYPE_CHAR
						lcCellValue = "StringValue"
						lcSetDate   = SET("DATE")
						SET DATE TO AMERICAN
						lnStringId = this.AddStringValue(tnWB, TRANSFORM(txCellValue), False)
						SET DATE TO (lcSetDate)
					ENDIF
					lcHorzAlign = CELL_HORIZ_ALIGN_LEFT
					lnNumFmtId  = CELL_FORMAT_DATE_MMDDYY
			
				CASE lcDataType = DATA_TYPE_DATETIME
					IF TTOD(txCellValue) >= DATE(1900, 1, 1)
						lnSetDecimals = SET("DECIMALS")
						SET DECIMALS TO 12
						lnWholePart = INT(TTOD(txCellValue) - DATE(1900, 1, 1) + 2)
						lnFractPart = ((HOUR(txCellValue) * 60 + MINUTE(txCellValue)) * 60 + SEC(txCellValue)) / (24 * 60 * 60)
						lcCellValue = TRANSFORM(lnWholePart + lnFractPart)
						SET DECIMALS TO (lnSetDecimals)
					ELSE
						lcDataType  = DATA_TYPE_CHAR
						lcCellValue = "StringValue"
						lcSetDate   = SET("DATE")
						SET DATE TO AMERICAN
						lnStringId = this.AddStringValue(tnWB, TRANSFORM(txCellValue), False)
						SET DATE TO (lcSetDate)
					ENDIF
					lcHorzAlign = CELL_HORIZ_ALIGN_LEFT
					lnNumFmtId  = CELL_FORMAT_DATETIME_MMMDDYYYY_TTAM
			
				CASE lcDataType = DATA_TYPE_FLOAT
					lcCellValue = TRANSFORM(txCellValue)
					IF ATC(".", lcCellValue) = 0
						lcDataType = DATA_TYPE_INT
					ENDIF
			
				CASE lcDataType = DATA_TYPE_CURRENCY
					IF this.SaveCurrencyAsNumeric
						lcDataType  = DATA_TYPE_FLOAT                     && Change requested by Tony Federer
						lcCellValue = TRANSFORM(MTON(txCellValue))
					ELSE
						lcCurrency  = SET("Currency", 1)
						lcCellValue = CHRTRAN(TRANSFORM(txCellValue), lcCurrency+",", "")
						lcHorzAlign = CELL_HORIZ_ALIGN_RIGHT
						DO CASE
							CASE lcCurrency = "£"
								lnNumFmtId = CELL_FORMAT_ACC_CURR_POUNDS
				
							CASE lcCurrency = "€"
								lnNumFmtId = CELL_FORMAT_ACC_CURR_EURO
				
							OTHERWISE
								lnNumFmtId = CELL_FORMAT_CURRENCY_PAREN
						ENDCASE
					ENDIF
			
				CASE lcDataType = DATA_TYPE_GENERAL
					lcCellValue = ""                                      && Change recommendation by Doug Hennig
			
				OTHERWISE
					lcDataType  = DATA_TYPE_GENERAL
					lcCellValue = TRANSFORM(txCellValue)
			ENDCASE
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, xl_cellxfs.fontid, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, xl_cellxfs.wraptext, xl_cellxfs.rotation)
					ELSE
						REPLACE xl_cellxfs.numFmtId WITH lnNumFmtId IN xl_cellxfs
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					IF lnNumFmtId = 0
						lnCellXfsId = xl_cells.cellxfs
					ELSE
						lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, 0, 0, 0, "", "", 0, 0, 0)
					ENDIF
				ENDIF
				REPLACE xl_cells.cellvalue   WITH lcCellValue, ;
						xl_cells.cellxfs     WITH lnCellXfsId, ;
						xl_cells.stringid    WITH lnStringId, ;
						xl_cells.datatype    WITH lcDataType, ;
						xl_cells.celldeleted WITH False IN xl_cells
			ELSE
				IF lnNumFmtId > 0
					lnCellXfsId = this.GetCellXfsId(tnWB, lnNumFmtId, 0, 0, 0, "", "", 0, 0, 0)
				ELSE
					lnCellXfsId = 0
				ENDIF
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, cellvalue, stringid, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, lcCellValue, lnStringId, lcDataType, lnCellXfsId, False, -1, 0)
			
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			DELETE FROM xl_formulas WHERE workbook = tnWB AND sheet = tnSheet AND cellrow = tnCellRow AND cellcol = tnCellCol
			this.RestoreVFPSettings()
		
		CATCH TO loException
			llReturn = False
		ENDTRY
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellWordWrap		&& Sets the cell word-wrapping value
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol, tlWordWrap
		LOCAL llReturn, lnWrapText, lnCellXfsId
		IF PCOUNT() < 5 .OR. VARTYPE(tlWordWrap) != "L" .OR. this.IsCellReferenceInvalid(tnCellRow, tnCellCol)
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			lnWrapText = IIF(tlWordWrap, 1, 0)
		*-*	Set the XfsId to the cell record
			IF this.GetCellRecord(tnWB, tnSheet, tnCellRow, tnCellCol)
				IF xl_cells.cellxfs > 0 .AND. this.GetCellXfsRecord(tnWB, xl_cells.cellxfs)
					lnCellXfsId = xl_cellxfs.id
					SELECT workbook FROM xl_cells WHERE workbook = tnWB AND cellxfs = lnCellXfsId INTO CURSOR xl_temp
					IF _TALLY > 1
						lnCellXfsId = this.GetCellXfsId(tnWB, xl_cellxfs.numFmtId, xl_cellxfs.fontId, xl_cellxfs.fillid, xl_cellxfs.borderId, xl_cellxfs.halign, xl_cellxfs.valign, xl_cellxfs.indent, lnWrapText, xl_cellxfs.rotation)
					ELSE
						REPLACE xl_cellxfs.wraptext WITH lnWrapText IN xl_cellxfs
					ENDIF
					USE IN SELECT('xl_temp')
				ELSE
					lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", 0, lnWrapText, 0)
				ENDIF
				REPLACE xl_cells.cellxfs WITH lnCellXfsId IN xl_cells
			ELSE
				lnCellXfsId = this.GetCellXfsId(tnWB, 0, 0, 0, 0, "", "", 0, lnWrapText, 0)
				INSERT INTO xl_cells (workbook, sheet, cellrow, cellcol, datatype, cellxfs, celldeleted, numdec, validndx) ;
					VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, DATA_TYPE_NONE, lnCellXfsId, False, -1, 0)
				this.SetRowMaxColumn(tnWB, tnSheet, tnCellRow, tnCellCol)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCellWordWrapRange		&& Sets the word wrap for a range of cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol, tlWordWrap
		LOCAL lnRow, lnCol, llReturn
		IF PCOUNT() < 7 .OR. VARTYPE(tlWordWrap) != "L" .OR. this.IsCellReferenceInvalid(tnBegRow, tnBegCol) .OR. this.IsCellReferenceInvalid(tnEndRow, tnEndCol)
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			FOR lnCol=tnBegCol TO tnEndCol
				IF !this.SetCellWordWrap(tnWB, tnSheet, lnRow, lnCol, tlWordWrap)
					llReturn = False
					EXIT
				ENDIF
			ENDFOR
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetColumnBestFit		&& Set the column to best fit
		LPARAMETERS tnWB, tnSheet, tnColumn, tlBestFit
		LOCAL llReturn
		IF PCOUNT() < 4
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
				REPLACE xl_columns.bestfit WITH tlBestFit IN xl_columns
			ELSE
				INSERT INTO xl_columns (workbook, sheet, col, bestfit) VALUES (tnWB, tnSheet, tnColumn, tlBestFit)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetColumnHidden		&& Sets the column hidden setting
		LPARAMETERS tnWB, tnSheet, tnColumn, tlHidden
		LOCAL llReturn, lcAlias, lnHidden
		IF PCOUNT() < 4 .OR. VARTYPE(tlHidden) != "L"
			RETURN False
		ENDIF
		lnHidden = IIF(tlHidden, 1, 0)
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
				REPLACE xl_columns.hidden WITH lnHidden IN xl_columns
			ELSE
				INSERT INTO xl_columns (workbook, sheet, col, hidden, bestfit) VALUES (tnWB, tnSheet, tnColumn, lnHidden, False)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetColumnWidth		&& Sets the column width
		LPARAMETERS tnWB, tnSheet, tnColumn, tnWidth
		LOCAL llReturn
		IF PCOUNT() < 4 .OR. VARTYPE(tnWidth) != "N"
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetColWidthKeyExpr(tnWB, tnSheet, tnColumn), "xl_columns", "col")
				REPLACE xl_columns.width WITH tnWidth IN xl_columns
			ELSE
				INSERT INTO xl_columns (workbook, sheet, col, width, bestfit) VALUES (tnWB, tnSheet, tnColumn, tnWidth, False)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetColumnWidthRange		&& Sets the column width for a range of columns
		LPARAMETERS tnWB, tnSheet, tnBegCol, tnEndCol, tnWidth
		LOCAL lnCol, llReturn
		IF PCOUNT() < 5
			RETURN False
		ENDIF
		IF tnBegCol > tnEndCol
			RETURN False
		ENDIF
		llReturn = True
		FOR lnCol=tnBegCol TO tnEndCol
			IF !this.SetColumnWidth(tnWB, tnSheet, lnCol, tnWidth)
				llReturn = False
				EXIT
			ENDIF
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetCustomPaperSize		&& Sets the paper size based on custom dimensions
		LPARAMETERS tnWB, tnSheet, tnWidth, tnHeight, tcDimen
		LOCAL llReturn
		IF PCOUNT() = 5 .AND. this.GetSheetRecord(tnWB, tnSheet)
			DO CASE
				CASE tnWidth=0 .OR. tnHeight=0
					REPLACE xl_sheets.paperwidth  WITH 0, ;
							xl_sheets.paperheight WITH 0, ;
							xl_sheets.paperdimen  WITH "" IN xl_sheets
					llReturn = True
		
				CASE tnWidth>0 .AND. tnHeight>0 .AND. INLIST(tcDimen, "mm", "in")
					REPLACE xl_sheets.paperwidth  WITH tnWidth, ;
							xl_sheets.paperheight WITH tnHeight, ;
							xl_sheets.paperdimen  WITH LOWER(tcDimen) IN xl_sheets
					llReturn = True
					
				OTHERWISE
					llReturn = False
			ENDCASE
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetDefaultBorder		&& Sets the default border style for the workbook
		LPARAMETERS tnWB, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		IF VARTYPE(tcLStyle) != "C"
			tcLStyle = BORDER_STYLE_NONE
		ENDIF
		DO CASE
			CASE PCOUNT() = 2
				tnLColor = 0
				tcRStyle = tcLStyle
				tnRColor = 0
				tcTStyle = tcLStyle
				tnTColor = 0
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 3
				tcRStyle = tcLStyle
				tnRColor = 0
				tcTStyle = tcLStyle
				tnTColor = 0
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 4
				tnRColor = 0
				tcTStyle = tcLStyle
				tnTColor = 0
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 5
				tcTStyle = tcLStyle
				tnTColor = 0
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 6
				tnTColor = 0
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 7
				tcBStyle = tcLStyle
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 8
				tnBColor = 0
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 9
				tcDStyle = tcLStyle
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 10
				tnDColor = 0
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 11
				tnDiagDn = 0
				tnDiagUp = 0
		
			CASE PCOUNT() = 12
				tnDiagUp = 0
		
		ENDCASE
		IF VARTYPE(tnLColor) != "N"
			tnLColor = 0
		ENDIF
		IF VARTYPE(tcRStyle) != "C"
			tcRStyle = tcLStyle
		ENDIF
		IF VARTYPE(tnRColor) != "N"
			tnRColor = 0
		ENDIF
		IF VARTYPE(tcTStyle) != "C"
			tcTStyle = tcLStyle
		ENDIF
		IF VARTYPE(tnTColor) != "N"
			tnTColor = 0
		ENDIF
		IF VARTYPE(tcBStyle) != "C"
			tcBStyle = tcLStyle
		ENDIF
		IF VARTYPE(tnBColor) != "N"
			tnBColor = 0
		ENDIF
		IF VARTYPE(tcDStyle) != "C"
			tcDStyle = tcLStyle
		ENDIF
		IF VARTYPE(tnDColor) != "N"
			tnDColor = 0
		ENDIF
		IF VARTYPE(tnDiagDn) != "N"
			tnDiagDn = 0
		ENDIF
		IF VARTYPE(tnDiagUp) != "N"
			tnDiagUp = 0
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(0), "xl_borders", "id")
			REPLACE xl_borders.lstyle WITH tcLStyle, ;
					xl_borders.lcolor WITH tnLColor, ;
					xl_borders.rstyle WITH tcRStyle, ;
					xl_borders.rcolor WITH tnRColor, ;
					xl_borders.tstyle WITH tcTStyle, ;
					xl_borders.tcolor WITH tnTColor, ;
					xl_borders.bstyle WITH tcBStyle, ;
					xl_borders.bcolor WITH tnBColor, ;
					xl_borders.dstyle WITH tcDStyle, ;
					xl_borders.dcolor WITH tnDColor, ;
					xl_borders.diagdn WITH tnDiagDn, ;
					xl_borders.diagup WITH tnDiagUp IN xl_borders
		ELSE
			INSERT INTO xl_borders (workbook, id, lstyle, lcolor, rstyle, rcolor, tstyle, tcolor, bstyle, bcolor, dstyle, dcolor, diagdn, diagup) ;
				VALUES (tnWB, 0, tcLStyle, tnLColor, tcRStyle, tnRColor, tcTStyle, tnTColor, tcBStyle, tnBColor, tcDStyle, tnDColor, tnDiagDn, tnDiagUp)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetDefaultFill		&& Sets the default fill for the workbook
		LPARAMETERS tnWB, tcPatternType, tnFColor, tnBColor
		LOCAL lnFIndex, lnBIndex
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		IF VARTYPE(tcPatternType) != "C"
			tcPatternType = FILL_STYLE_NONE
		ENDIF
		DO CASE
			CASE PCOUNT() = 2
				tnFColor = 0
				lnFIndex = 0
				tnBColor = 0
				lnBIndex = 0
		
			CASE PCOUNT() = 3
				tnBColor = 0
				lnBIndex = 64
		ENDCASE
		IF VARTYPE(tnFColor) != "N"
			tnFColor = 0
		ENDIF
		IF VARTYPE(tnBColor) != "N"
			tnBColor = RGB(255,255,255)
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(0), "xl_fills", "id")
			REPLACE xl_fills.fgcolor   WITH tnFColor, ;
					xl_fills.bgcolor   WITH tnBColor, ;
					xl_fills.patttype  WITH tcPatternType, ;
					xl_fills.theme     WITH 0, ;
					xl_fills.tint      WITH 0, ;
					xl_fills.fgindexed WITH 0, ;
					xl_fills.bgindexed WITH 0 IN xl_fills
		ELSE
			INSERT INTO xl_fills (workbook, id, fgcolor, bgcolor, patttype, theme, tint, fgindexed, bgindexed, fgtheme, bgtheme) ;
				VALUES (tnWB, 0, tnFColor, tnBColor, tcPatternType, 0, 0, lnFIndex, lnBIndex, -1, -1)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetDefaultFont		&& Sets the default font for the workbook
		LPARAMETERS tnWB, tcFontName, tnSize, tlBold, tlItalic, tnColor, tcUline, tlStrkthr, tcFVPos, tnTheme, tnTint, tnIndexed
		IF PCOUNT() < 2
			RETURN False
		ENDIF
		DO CASE
			CASE PCOUNT() = 2
				tnSize    = 11
				tlBold    = False
				tlItalic  = False
				tnColor   = 0
				tcUline   = ""
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 3
				tlBold    = False
				tlItalic  = False
				tnColor   = 0
				tcUline   = ""
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 4
				tlItalic  = False
				tnColor   = 0
				tcUline   = ""
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 5
				tnColor   = 0
				tcUline   = ""
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 6
				tcUline   = ""
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 7
				tlStrkthr = False
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 8
				tcFVPos   = ""
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 9
				tnTheme   = 0
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 10
				tnTint    = 0
				tnIndexed = 0
		
			CASE PCOUNT() = 11
				tnIndexed = 0
		ENDCASE
		IF VARTYPE(tcFontName) != "C"
			tcFontName = "Calibri"
		ENDIF
		IF VARTYPE(tnSize) != "N"
			tnSize = 11
		ENDIF
		IF VARTYPE(tlBold) != "L"
			tlBold = False
		ENDIF
		IF VARTYPE(tlItalic) != "L"
			tlItalic = False
		ENDIF
		IF VARTYPE(tnColor) != "N"
			tnColor = 0
		ENDIF
		IF VARTYPE(tcUline) != "C"
			tcUline = ""
		ENDIF
		IF VARTYPE(tlStrkthr) != "L"
			tlStrkthr = False
		ENDIF
		IF VARTYPE(tcFVPos) != "C"
			tcFVPos = ""
		ENDIF
		IF VARTYPE(tnTheme) != "N"
			tnTheme = 0
		ENDIF
		IF VARTYPE(tnTint) != "N"
			tnTint = 0
		ENDIF
		IF VARTYPE(tnIndexed) != "N"
			tnTint = 0
		ENDIF
		IF SEEK(BINTOC(tnWB)+BINTOC(0), "xl_fonts", "id")
			REPLACE xl_fonts.fname   WITH tcFontName, ;
					xl_fonts.fsize   WITH tnSize, ;
					xl_fonts.fbold   WITH tlBold, ;
					xl_fonts.fitalic WITH tlItalic, ;
					xl_fonts.fcolor  WITH tnColo , ;
					xl_fonts.uline   WITH tcUline, ;
					xl_fonts.strkthr WITH tlStrkthr, ;
					xl_fonts.fvpos   WITH tcFVPos, ;
					xl_fonts.theme   WITH tnTheme, ;
					xl_fonts.tint    WITH tnTint, ;
					xl_fonts.indexed WITH tnIndexed IN xl_fonts
		ELSE
			INSERT INTO xl_fonts (workbook, id, fname, fsize, fbold, fitalic, fcolor, uline, strkthr, fvpos, theme, tint, indexed) ;
				VALUES (tnWB, 0, tcFontName, tnSize, tlBold, tlItalic, tnColor, tcUline, tlStrkthr, tcFVPos, tnTheme, tnTint, tnIndexed)
		ENDIF
		RETURN true
	ENDPROC

	PROCEDURE SetDisplayGridLines		&& Sets the sheet display grid lines setting
		LPARAMETERS tnWB, tnSheet, tlGridLines
		IF PCOUNT() = 3 .AND. VARTYPE(tlGridLines) = "L" .AND. this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.gridlines WITH IIF(tlGridLines, 1, 0) IN xl_sheets
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE SetHeaderFooterSetup		&& Sets the header and footer setup for the sheet
		LPARAMETERS tnWB, tnSheet, tlAlignMargin, tlDiffFirstPg, tlDiffOddEven, tlScaleWDoc
		LOCAL llReturn
		IF PCOUNT() = 6 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_hdrfooterdefn", "wbsheet")
				REPLACE xl_hdrfooterdefn.alignmargin WITH tlAlignMargin, ;
						xl_hdrfooterdefn.difffirstpg WITH tlDiffFirstPg, ;
						xl_hdrfooterdefn.diffoddeven WITH tlDiffOddEven, ;
						xl_hdrfooterdefn.scalewdoc   WITH tlScaleWDoc IN xl_hdrfooterdefn
			ELSE
				INSERT INTO xl_hdrfooterdefn (workbook, sheet, alignmargin, difffirstpg, diffoddeven, scalewdoc) ;
					VALUES (tnWB, tnSheet, tlAlignMargin, tlDiffFirstPg, tlDiffOddEven, tlScaleWDoc)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetHeaderFooterText		&& Sets the header/footer text for the sheet
		LPARAMETERS tnWB, tnSheet, tnPage, tnSection, tcText, tcFontName, tnFontSize, tnFontEffect, tnFontColor
		LOCAL llReturn
		DO CASE
			CASE PCOUNT() < 5
				RETURN False
		
			CASE PCOUNT() = 5
				tcFontName   = ""
				tnFontSize   = 0
				tnFontEffect = 0
				tnFontColor  = 0
		
			CASE PCOUNT() = 6
				tnFontSize   = 0
				tnFontEffect = 0
				tnFontColor  = 0
		
			CASE PCOUNT() = 7
				tnFontEffect = 0
				tnFontColor  = 0
		
			CASE PCOUNT() = 8
				tnFontColor = 0
		ENDCASE
		IF VARTYPE(tcFontName) != "C"
			tcFontName = ""
		ENDIF
		IF VARTYPE(tnFontSize) != "N"
			tnFontSize = 0
		ENDIF
		IF VARTYPE(tnFontEffect) != "N"
			tnFontEffect = 0
		ENDIF
		IF VARTYPE(tnFontColor) != "N"
			tnFontColor = 0
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_hdrfooterdefn", "wbsheet")
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, tnPage, tnSection), "xl_hdrfootertext", "hdrfootr")
					REPLACE xl_hdrfootertext.text      WITH tcText, ;
							xl_hdrfootertext.fontname  WITH tcFontName, ;
							xl_hdrfootertext.fontsize  WITH tnFontSize, ;
							xl_hdrfootertext.fontstyle WITH tnFontEffect, ;
							xl_hdrfootertext.fontcolor WITH tnFontColor IN xl_hdrfootertext
				ELSE
					INSERT INTO xl_hdrfootertext (workbook, sheet, page, section, text, fontname, fontsize, fontstyle, fontcolor) ;
						VALUES (tnWB, tnSheet, tnPage, tnSection, tcText, tcFontName, tnFontSize, tnFontEffect, tnFontColor)
				ENDIF
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROTECTED PROCEDURE SetLastId		&& Sets the last used Id Value for strings, borders, fills, fonts, and cellxfs
		LPARAMETERS tnWB, tnSheet, tnLastId, tcCursor
		LOCAL lnNdx
		DO CASE
			CASE tcCursor = "global"
				lnNdx = ASCAN(this.GlobalId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.GlobalId[lnNdx, 2] = tnLastId
				ELSE
					IF this.GlobalId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.GlobalId, 1) + 1
					ENDIF
					DIMENSION this.GlobalId[lnNdx, 2]
					this.GlobalId[lnNdx, 1] = tnWB
					this.GlobalId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_borders"
				lnNdx = ASCAN(this.LastBorderId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastBorderId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastBorderId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastBorderId, 1) + 1
					ENDIF
					DIMENSION this.LastBorderId[lnNdx, 2]
					this.LastBorderId[lnNdx, 1] = tnWB
					this.LastBorderId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_cellxfs"
				lnNdx = ASCAN(this.LastCellXfsId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastCellXfsId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastCellXfsId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastCellXfsId, 1) + 1
					ENDIF
					DIMENSION this.LastCellXfsId[lnNdx, 2]
					this.LastCellXfsId[lnNdx, 1] = tnWB
					this.LastCellXfsId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_fills"
				lnNdx = ASCAN(this.LastFillId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastFillId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastFillId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastFillId, 1) + 1
					ENDIF
					DIMENSION this.LastFillId[lnNdx, 2]
					this.LastFillId[lnNdx, 1] = tnWB
					this.LastFillId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_fonts"
				lnNdx = ASCAN(this.LastFontId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastFontId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastFontId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastFontId, 1) + 1
					ENDIF
					DIMENSION this.LastFontId[lnNdx, 2]
					this.LastFontId[lnNdx, 1] = tnWB
					this.LastFontId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_sheet_rels"
				lcKey = BINTOC(tnWB)+BINTOC(tnSheet)
				lnNdx = ASCAN(this.LastSheetRelsId, lcKey, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastSheetRelsId[lnNdx, 2] = tnLastId
				ELSE
					IF EMPTY(this.LastSheetRelsId[1, 1])
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastSheetRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastSheetRelsId[lnNdx, 2]
					this.LastSheetRelsId[lnNdx, 1] = lcKey
					this.LastSheetRelsId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_drawing_rels"
				lcKey = BINTOC(tnWB)+BINTOC(tnSheet)
				lnNdx = ASCAN(this.LastDrawingRelsId, lcKey, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastDrawingRelsId[lnNdx, 2] = tnLastId
				ELSE
					IF EMPTY(this.LastDrawingRelsId[1, 1])
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastDrawingRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastDrawingRelsId[lnNdx, 2]
					this.LastDrawingRelsId[lnNdx, 1] = lcKey
					this.LastDrawingRelsId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_ndxcolors"
				lnNdx = ASCAN(this.LastIndexColorId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastIndexColorId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastIndexColorId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastIndexColorId, 1) + 1
					ENDIF
					DIMENSION this.LastIndexColorId[lnNdx, 2]
					this.LastIndexColorId[lnNdx, 1] = tnWB
					this.LastIndexColorId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_mrucolors"
				lnNdx = ASCAN(this.LastMruColorId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastMruColorId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastMruColorId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastMruColorId, 1) + 1
					ENDIF
					DIMENSION this.LastMruColorId[lnNdx, 2]
					this.LastMruColorId[lnNdx, 1] = tnWB
					this.LastMruColorId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_numfmts"
				lnNdx = ASCAN(this.LastNumFmtId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastNumFmtId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastNumFmtId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastNumFmtId, 1) + 1
					ENDIF
					DIMENSION this.LastNumFmtId[lnNdx, 2]
					this.LastNumFmtId[lnNdx, 1] = tnWB
					this.LastNumFmtId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_workbook_rels"
				lnNdx = ASCAN(this.LastWorkbookRelsId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastWorkbookRelsId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastWorkbookRelsId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastWorkbookRelsId, 1) + 1
					ENDIF
					DIMENSION this.LastWorkbookRelsId[lnNdx, 2]
					this.LastWorkbookRelsId[lnNdx, 1] = tnWB
					this.LastWorkbookRelsId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_sheets"
				lnNdx = ASCAN(this.LastSheetId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastSheetId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastSheetId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastSheetId, 1) + 1
					ENDIF
					DIMENSION this.LastSheetId[lnNdx, 2]
					this.LastSheetId[lnNdx, 1] = tnWB
					this.LastSheetId[lnNdx, 2] = tnLastId
				ENDIF
		
			CASE tcCursor = "xl_strings"
				lnNdx = ASCAN(this.LastStringId, tnWB, 1, -1, 1, 15)
				IF lnNdx > 0
					this.LastStringId[lnNdx, 2] = tnLastId
				ELSE
					IF this.LastStringId[1, 1] = 0
						lnNdx = 1
					ELSE
						lnNdx = ALEN(this.LastStringId, 1) + 1
					ENDIF
					DIMENSION this.LastStringId[lnNdx, 2]
					this.LastStringId[lnNdx, 1] = tnWB
					this.LastStringId[lnNdx, 2] = tnLastId
				ENDIF
		
			OTHERWISE
				SET STEP ON
		ENDCASE
	ENDPROC

	PROCEDURE SetPaperSize		&& Sets the paper size for the selected sheet
		LPARAMETERS tnWB, tnSheet, tnPaperSize
		LOCAL llReturn, lcAlias
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF BETWEEN(tnPaperSize, 0, 118)
				REPLACE xl_sheets.papersize WITH tnPaperSize IN xl_sheets
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetPrintArea		&& Sets the print area for the sheet
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		IF PCOUNT() < 6
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow .OR. tnBegCol > tnEndCol
			RETURN False
		ENDIF
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook") .AND. SEEK(BINTOC(tnWB)+PADL("_xlnm.PRINT_AREA", 50, " "), "xl_namerange", "wbrname")
			UPDATE xl_namerange ;
				SET begrow = tnBegRow, ;
					begcol = tnBegCol, ;
					endrow = tnEndRow, ;
					endcol = tnEndCol ;
				WHERE workbook = tnWB ;
					AND sheet = tnSheet ;
					AND rname = PADL("_xlnm.PRINT_AREA", 50, " ")
		ELSE
			INSERT INTO xl_namerange (workbook, sheet, rname, scope, begrow, begcol, endrow, endcol) ;
				VALUES (tnWB, tnSheet, "_xlnm.PRINT_AREA", SCOPE_SH_NAMED_RANGE, tnBegRow, tnBegCol, tnEndRow, tnEndCol)
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetPrintFitToHeight		&& Number of vertical pages to fit on for printing
		LPARAMETERS tnWB, tnSheet, tnFitToHeight
		LOCAL llReturn
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF INT(tnFitToHeight) >= 0
				REPLACE xl_sheets.fittoheight WITH INT(tnFitToHeight) IN xl_sheets
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetPrintFitToWidth		&& Number of horizontal pages to fit on for printing
		LPARAMETERS tnWB, tnSheet, tnFitToWidth
		LOCAL llReturn
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF INT(tnFitToWidth) >= 0
				REPLACE xl_sheets.fittowidth WITH INT(tnFitToWidth) IN xl_sheets
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetPrintOrientation		&& Sets the printer orientation for sheet output
		LPARAMETERS tnWB, tnSheet, tnOrientation
		LOCAL llReturn
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.prnorient WITH tnOrientation IN xl_sheets
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetRowHeight		&& Sets the row height
		LPARAMETERS tnWB, tnSheet, tnRow, tnHeight
		LOCAL llReturn
		IF PCOUNT() < 4 .OR. VARTYPE(tnHeight) != "N"
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(this.GetRowHeightKeyExpr(tnWB, tnSheet, tnRow), "xl_rows", "row")
				REPLACE xl_rows.height WITH tnHeight IN xl_rows
			ELSE
				INSERT INTO xl_rows (workbook, sheet, row, height, hidden) VALUES (tnWB, tnSheet, tnRow, tnHeight, 0)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetRowHeightRange		&& Sets the row height for a range of rows
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnEndRow, tnHeight
		LOCAL lnRow, llReturn
		IF PCOUNT() < 5
			RETURN False
		ENDIF
		IF tnBegRow > tnEndRow
			RETURN False
		ENDIF
		llReturn = True
		FOR lnRow=tnBegRow TO tnEndRow
			IF !this.SetRowHeight(tnWB, tnSheet, lnRow, tnHeight)
				llReturn = False
				EXIT
			ENDIF
		ENDFOR
		RETURN llReturn
	ENDPROC

	PROCEDURE SetRowHidden		&& Sets the row hidden setting
		LPARAMETERS tnWB, tnSheet, tnRow, tlHidden
		LOCAL llReturn, lnHidden
		LOCAL ARRAY laRowRecord[1]
		IF PCOUNT() < 4 .OR. VARTYPE(tlHidden) != "L"
			RETURN False
		ENDIF
		lnHidden = IIF(tlHidden, 1, 0)
		IF this.GetSheetRecord(tnWB, tnSheet)
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnRow), "xl_rows", "row")
				REPLACE xl_rows.hidden WITH lnHidden IN xl_rows
			ELSE
				SELECT height, rowlevel, collapsed FROM xl_rows WHERE workbook = tnWB AND sheet = tnSheet AND row = tnRow-1 INTO ARRAY laRowRecord
				INSERT INTO xl_rows (workbook, sheet, row, height, rowlevel, collapsed, hidden) ;
					VALUES (tnWB, tnSheet, tnRow, NVL(laRowRecord[1, 1], -1), NVL(laRowRecord[1, 2], 0), NVL(laRowRecord[1, 3], False), lnHidden)
			ENDIF
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROTECTED PROCEDURE SetRowMaxColumn		&& Sets the value for the maximum cell column in a row
		LPARAMETERS tnWB, tnSheet, tnCellRow, tnCellCol
		IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(tnCellRow), "xl_rows", "row")
			IF xl_rows.maxcol < tnCellCol
				REPLACE xl_rows.maxcol WITH tnCellCol IN xl_rows
			ENDIF
		ELSE
			INSERT INTO xl_rows (workbook, sheet, row, maxcol, height, hidden) VALUES (tnWB, tnSheet, tnCellRow, tnCellCol, -1.00, 0)
		ENDIF
	ENDPROC

	PROCEDURE SetSheetGroupSettings		&& Sets the group settings for a given sheet
		LPARAMETERS tnWB, tnSheet, tlSummaryBelow, tlSummaryRight
		LOCAL llReturn
		DO CASE
			CASE PCOUNT() = 0 .OR. PCOUNT() = 1
				RETURN False
		
			CASE PCOUNT() = 2
				tlSummaryBelow = True
				tlSummaryRight = True
			
			CASE PCOUNT() = 3
				tlSummaryRight = True
		ENDCASE
		IF VARTYPE(tlSummaryBelow) != "L" .OR.  VARTYPE(tlSummaryRight) != "L"
			RETURN False
		ENDIF
		IF this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.grpsumbelow WITH IIF(tlSummaryBelow, 0, 1), ;
					xl_sheets.grpsumright WITH IIF(tlSummaryRight, 0, 1) IN xl_sheets
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetSheetMargins		&& Sets the sheet margin values
		LPARAMETERS tnWB, tnSheet, tnLeft, tnRight, tnTop, tnbot, tnHeader, tnFooter
		LOCAL llReturn
		IF PCOUNT() = 8 .AND. this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.mleft   WITH tnLeft, ;
					xl_sheets.mright  WITH tnRight, ;
					xl_sheets.mtop    WITH tnTop, ;
					xl_sheets.mbot    WITH tnbot, ;
					xl_sheets.mheader WITH tnHeader, ;
					xl_sheets.mfooter WITH tnFooter IN xl_sheets
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetSheetPrintOptions		&& Sets the sheet print options
		LPARAMETERS tnWB, tnSheet, txPrnOptCenH, txPrnOptCenV, txPrnOptHead, txPrnOptGrid
		LOCAL llReturn, loException
		DO CASE
			CASE PCOUNT() < 3
				RETURN False
		
			CASE PCOUNT() = 3
				tnPrnOptCenV = 0
				tnPrnOptHead = 0
				tnPrnOptGrid = 0
		
			CASE PCOUNT() = 4
				tnPrnOptHead = 0
				tnPrnOptGrid = 0
		
			CASE PCOUNT() = 5
				tnPrnOptGrid = 0
		ENDCASE
		TRY
			IF this.GetSheetRecord(tnWB, tnSheet)
				txPrnOptCenH = CAST(txPrnOptCenH AS INTEGER)
				txPrnOptCenV = CAST(txPrnOptCenV AS INTEGER)
				txPrnOptHead = CAST(txPrnOptHead AS INTEGER)
				txPrnOptGrid = CAST(txPrnOptGrid AS INTEGER)
		
				REPLACE xl_sheets.prnopthead WITH txPrnOptHead, ;
						xl_sheets.prnoptgrid WITH txPrnOptGrid, ;
						xl_sheets.prnoptctrh WITH txPrnOptCenH, ;
						xl_sheets.prnoptctrv WITH txPrnOptCenV IN xl_sheets
		
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		
		CATCH TO loException
			llReturn = False
		ENDTRY
		RETURN llReturn
	ENDPROC

	PROCEDURE SetSheetProtection		&& Sets the sheet protection on or off
		LPARAMETERS tnWB, tnSheet, toProtection
		LOCAL llReturn
		IF VARTYPE(toProtection) = "O" .AND. PEMSTATUS(toProtection, "Locked", 5) .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF toProtection.Locked .AND. EMPTY(toProtection.Password)
				toProtection.Password = this.DefaultPW
			ENDIF
			TRY
				BEGIN TRANSACTION
					REPLACE xl_sheets.protection  WITH toProtection.Locked, ;
							xl_sheets.algorithm   WITH toProtection.AlgorithmName, ;
							xl_sheets.password    WITH toProtection.Password, ;
							xl_sheets.autoFilter  WITH toProtection.AutoFilter, ;
							xl_sheets.deletecol   WITH toProtection.DeleteColumns, ;
							xl_sheets.deleterow   WITH toProtection.DeleteRows, ;
							xl_sheets.formatcell  WITH toProtection.FormatCells, ;
							xl_sheets.formatcol   WITH toProtection.FormatColumns, ;
							xl_sheets.formatrow   WITH toProtection.FormatRows, ;
							xl_sheets.insertcol   WITH toProtection.InsertColumns, ;
							xl_sheets.insertrow   WITH toProtection.InsertRows, ;
							xl_sheets.inserthyper WITH toProtection.InsertHyperlinks, ;
							xl_sheets.pivottbl    WITH toProtection.pivotTables, ;
							xl_sheets.Sellocked   WITH toProtection.SelectLockedCells, ;
							xl_sheets.Selunlocked WITH toProtection.SelectUnlockedCells, ;
							xl_sheets.sort        WITH toProtection.Sort, ;
							xl_sheets.objects     WITH toProtection.Objects, ;
							xl_sheets.scenarios   WITH toProtection.Scenarios IN xl_sheets
				END TRANSACTION
				llReturn = True
		
			CATCH TO loException
				ROLLBACK
				llReturn = False
			ENDTRY
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetSheetScale		&& Sets the print scale; must be between 10 and 400; i.e. 10=10%, 100=100%, etc.
		LPARAMETERS tnWB, tnSheet, tnScale
		LOCAL llReturn
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF BETWEEN(INT(tnScale), 10, 400)
				REPLACE xl_sheets.scale WITH INT(tnScale) IN xl_sheets
				llReturn = True
			ELSE
				llReturn = False
			ENDIF
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetSheetVisibility		&& Sets the sheet visibility
		LPARAMETERS tnWB, tnSheet, tnVisiblity
		LOCAL llReturn
		llReturn = False
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			IF BETWEEN(tnVisiblity, VISIBLE_SHEET_STATE, VERYHIDDEN_SHEET_STATE)
				REPLACE xl_sheets.state WITH tnVisiblity IN xl_sheets
				llReturn = True
			ENDIF
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE SetTabColor		&& Sets the sheet tab color
		LPARAMETERS tnWB, tnSheet, tnRBGColor
		IF PCOUNT() = 3 .AND. this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.tabcolorndx WITH 0, ;
					xl_sheets.tabcolorrgb WITH this.ConvertColorToHex(tnRBGColor) IN xl_sheets
		ELSE
			RETURN False
		ENDIF
		RETURN True
	ENDPROC

	PROCEDURE SetWorkbookProtection		&& Sets the Workbook protection
		LPARAMETERS tnWB, tlLocked, tcPassword, tcAlgorithm
		IF SEEK(BINTOC(tnWB), "xl_workbooks", "workbook")
			DO CASE
				CASE PCOUNT() = 0
					RETURN False
		
				CASE PCOUNT() = 1
					tlLocked    = False
					tcPassword  = ""
					tcAlgorithm = ""
		
				CASE PCOUNT() = 2
					IF tlLocked
						tcPassword  = "F771"       && encoded vfpxworkbookxlsx
						tcAlgorithm = ""
					ELSE
						tcPassword  = ""
						tcAlgorithm = ""
					ENDIF
		
				CASE PCOUNT() = 3
					tcAlgorithm = ""
			ENDCASE
			REPLACE xl_workbooks.protection WITH tlLocked, ;
					xl_workbooks.password   WITH tcPassword, ;
					xl_workbooks.algorithm  WITH tcAlgorithm IN xl_workbooks
					
			RETURN True
		ELSE
			RETURN False
		ENDIF
	ENDPROC

	PROCEDURE TestCode		&& Method to test code for debugging
		LOCAL llCreated, lcZipPath, lcTempZip
		
		lcZipPath = "E:\My Work\FoxPro\Projects\WorkbookXLSX\Test Spreadsheets\Test\Temp"
		this.SaveAsUTF8(lcZipPath + "\xl\worksheets\sheet1.xml")
		
		lcTempZip = "E:\My Work\FoxPro\Projects\WorkbookXLSX\Test Spreadsheets\Test2\UTFWriteTest.zip"
		llCreated = this.AddFilesToZip(lcZipPath, lcTempZip)
		IF llCreated
			apiMoveFile(lcTempZip, "E:\My Work\FoxPro\Projects\WorkbookXLSX\Test Spreadsheets\Test\UTFWriteTest.xlsx")
		ENDIF
		
	ENDPROC

	PROTECTED PROCEDURE TTOC3		&& Equlivalent of TTOC() function for pre VFP9 support
		LPARAMETERS ttime
		LOCAL lct
		IF PCOUNT() < 1 OR VARTYPE(m.ttime) != "T"
			RETURN
		ENDIF
		lct = TTOC(m.ttime,1)
		RETURN LEFT(m.lct,4) + "-" + SUBSTR(m.lct,5,2) + "-" + SUBSTR(m.lct,7,2) + "T" + SUBSTR(m.lct,9,2) + ":" + SUBSTR(m.lct,11,2) + ":" + RIGHT(m.lct,2)
	ENDPROC

	PROCEDURE UnFreezePanes		&& Removes the pane freeze from the sheet
		LPARAMETERS tnWB, tnSheet
		LOCAL llReturn, lcAlias
		IF this.GetSheetRecord(tnWB, tnSheet)
			REPLACE xl_sheets.xsplit WITH 0, ;
					xl_sheets.ysplit WITH 0 IN xl_sheets
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		RETURN llReturn
	ENDPROC

	PROCEDURE UnGroupByColumn		&& Removes a data group level by columns
		LPARAMETERS tnWB, tnSheet, tnBegCol, tnEndCol
		LOCAL lnCol, lnLevel
		LOCAL ARRAY laLevel[1]
		IF PCOUNT() != 4 .OR. VARTYPE(tnBegCol) != "N" .OR. VARTYPE(tnEndCol) != "N"
			RETURN False
		ENDIF
		SELECT MAX(collevel) FROM xl_columns WHERE BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) .AND. col >= tnBegCol .AND. col <= tnEndCol INTO ARRAY laLevel
		lnLevel = NVL(laLevel[1], 0) - 1
		FOR lnCol=tnBegCol TO tnEndCol
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(lnCol), "xl_columns", "col")
				REPLACE xl_columns.collevel WITH lnLevel IN xl_columns
			ENDIF
		ENDFOR
		RETURN True
	ENDPROC

	PROCEDURE UnGroupByRow		&& Removes a data group level by rows
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnEndRow
		LOCAL lnRow, lnLevel
		LOCAL ARRAY laLevel[1]
		IF PCOUNT() != 4 .OR. VARTYPE(tnBegRow) != "N" .OR. VARTYPE(tnEndRow) != "N"
			RETURN False
		ENDIF
		SELECT MAX(rowlevel) FROM xl_rows WHERE BINTOC(workbook)+BINTOC(sheet) = BINTOC(tnWB)+BINTOC(tnSheet) .AND. row >= tnBegRow .AND. row <= tnEndRow INTO ARRAY laLevel
		lnLevel = MAX(NVL(laLevel[1], 0) - 1, 0)
		FOR lnRow=tnBegRow TO tnEndRow
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(lnRow), "xl_rows", "row")
				REPLACE xl_rows.rowlevel WITH lnLevel IN xl_rows
			ENDIF
		ENDFOR
		RETURN True
	ENDPROC

	PROCEDURE UnMergedCells		&& Unmerges merged cells
		LPARAMETERS tnWB, tnSheet, tnBegRow, tnBegCol, tnEndRow, tnEndCol
		LOCAL llReturn, lcAlias
		lcAlias = ALIAS()
		SELECT xl_mergecells
		LOCATE FOR workbook=tnWB .AND. sheet=tnSheet .AND. begrow=tnBegRow .AND. begcol=tnBegCol .AND. endrow=tnEndRow .AND. endcol=tnEndCol .AND. !DELETED()
		IF FOUND()
			DELETE IN xl_mergecells
			llReturn = True
		ELSE
			llReturn = False
		ENDIF
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		RETURN llReturn
	ENDPROC

	PROTECTED PROCEDURE WriteAsInLineFormattedString		&& Writes the string as formatted in-line
		LPARAMETERS thFile, tnRow, tnCol, txCellValue
		FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="inlineStr">')
		FWRITE(thFile, '<is>')
		FWRITE(thFile, '<r>')
		FWRITE(thFile, '<rPr>')
		FWRITE(thFile, '<sz val="' + TRANSFORM(this.DefaultFontSize) + '"/>')
		FWRITE(thFile, '<rFont val="' + this.DefaultFont + '"/>')
		FWRITE(thFile, '</rPr>')
		FWRITE(thFile, '<t>' + this.GetXMLString(txCellValue) + '</t>')
		FWRITE(thFile, '</r>')
		FWRITE(thFile, '</is>')
		FWRITE(thFile, '</c>')
	ENDPROC

	PROTECTED PROCEDURE WriteAsInLineString		&& Writes the value as an inlineStr value
		LPARAMETERS thFile, tnRow, tnCol, txCellValue
		FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="inlineStr">')
		FWRITE(thFile, '<is><t>' + this.GetXMLString(txCellValue) + '</t></is>')
		FWRITE(thFile, '</c>')
	ENDPROC

	PROTECTED PROCEDURE WriteDirectCellFormattedValue		&& WriteDirectCellFormattedValue
		LPARAMETERS thFile, tnRow, tnCol, txCellValue
		DO CASE
			CASE ISNULL(txCellValue)
				RETURN
		
			CASE INLIST(VARTYPE(txCellValue), "C", "D", "T") .AND. EMPTY(txCellValue)
				RETURN
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_DATE
				this.WriteAsInLineFormattedString(thFile, tnRow, tnCol, TRANSFORM(txCellValue))
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_DATETIME
				this.WriteAsInLineFormattedString(thFile, tnRow, tnCol, TTOC(txCellValue))
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_LOGICAL
				txCellValue = IIF(txCellValue, GETWORDNUM(this.TrueFalseValue, 1, "|"), GETWORDNUM(this.TrueFalseValue, 2, "|"))
				this.WriteAsInLineFormattedString(thFile, tnRow, tnCol, txCellValue)
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_CURRENCY
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="n">')
				FWRITE(thFile, '<v>' + TRANSFORM(MTON(txCellValue)) + '</v></c>')
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_CHAR
				this.WriteAsInLineFormattedString(thFile, tnRow, tnCol, ALLTRIM(txCellValue))
		
			CASE INLIST(VARTYPE(txCellValue), DATA_TYPE_INT, DATA_TYPE_FLOAT)
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="n">')
				FWRITE(thFile, '<v>' + TRANSFORM(txCellValue) + '</v></c>')
		
			OTHERWISE
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="inlineStr">')
				FWRITE(thFile, '<is><t>Unsupported Field Type</t></is></c>')
		ENDCASE
	ENDPROC

	PROTECTED PROCEDURE WriteDirectCellValue		&& Writes the cell value directly to the sheets xml
		LPARAMETERS thFile, tnRow, tnCol, txCellValue
		DO CASE
			CASE ISNULL(txCellValue)
				RETURN
		
			CASE INLIST(VARTYPE(txCellValue), "C", "D", "T") .AND. EMPTY(txCellValue)
				RETURN
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_DATE
				this.WriteAsInLineString(thFile, tnRow, tnCol, TRANSFORM(txCellValue))
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_DATETIME
				this.WriteAsInLineString(thFile, tnRow, tnCol, TTOC(txCellValue))
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_LOGICAL
				txCellValue = IIF(txCellValue, GETWORDNUM(this.TrueFalseValue, 1, "|"), GETWORDNUM(this.TrueFalseValue, 2, "|"))
				this.WriteAsInLineString(thFile, tnRow, tnCol, txCellValue)
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_CURRENCY
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="n">')
				FWRITE(thFile, '<v>' + TRANSFORM(MTON(txCellValue)) + '</v></c>')
		
			CASE VARTYPE(txCellValue) = DATA_TYPE_CHAR
				this.WriteAsInLineString(thFile, tnRow, tnCol, ALLTRIM(txCellValue))
		
			CASE INLIST(VARTYPE(txCellValue), DATA_TYPE_INT, DATA_TYPE_FLOAT)
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="n">')
				FWRITE(thFile, '<v>' + TRANSFORM(txCellValue) + '</v></c>')
		
			OTHERWISE
				FWRITE(thFile, '<c r="' + this.ColumnIndexToAscii(tnCol) + TRANSFORM(tnRow) + '" t="inlineStr">')
				FWRITE(thFile, '<is><t>Unsupported Field Type</t></is></c>')
		ENDCASE
	ENDPROC

	PROTECTED PROCEDURE WriteDirectXMLs		&& Writes the workbook XMLs for direct table/grid to workbook output
		LPARAMETERS tcPath, taSheetNames, tnNumSheets
		LOCAL lhFile, lcFileName, loException, lcSheetName, lnSheet
		EXTERNAL ARRAY taSheetNames
		*-*	Create the supporting XML files
		loException = .NULL.
		TRY
			lhFile = FCREATE(tcPath + "[Content_Types].xml")
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">')
			FWRITE(lhFile, '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>')
			FWRITE(lhFile, '<Default Extension="xml" ContentType="application/xml"/>')
			FWRITE(lhFile, '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/>')
			FWRITE(lhFile, '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/>')
			FOR lnSheet=1 TO tnNumSheets
				FWRITE(lhFile, '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet' + TRANSFORM(lnSheet) + '.xml"/>')
			ENDFOR
			FWRITE(lhFile, '<Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/>')
			FWRITE(lhFile, '<Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/>')
			FWRITE(lhFile, '</Types>')
			FCLOSE(lhFile)
		
		*-*	Save the .rels XML
			lhFile = FCREATE(tcPath + "_rels\.rels")
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
			FWRITE(lhFile, '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>')
			FWRITE(lhFile, '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>')
			FWRITE(lhFile, '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>')
			FWRITE(lhFile, '</Relationships>')
			FCLOSE(lhFile)
		
		*-*	Write the workbook relationships file
			lhFile = FCREATE(tcPath + "xl\_rels\workbook.xml.rels")
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
			FWRITE(lhFile, '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>')
			FOR lnSheet=1 TO tnNumSheets
				FWRITE(lhFile, '<Relationship Id="rId' + TRANSFORM(lnSheet+1) + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet' + TRANSFORM(lnSheet) + '.xml"/>')
			ENDFOR
			FWRITE(lhFile, '</Relationships>')
			FCLOSE(lhFile)
		
		*-*	Save the app XML
			lcFileName = tcPath + "docProps\app.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">')
			FWRITE(lhFile, '<Application>ExcelXMLX Release ' + TRANSFORM(this.ExcelXlsxRelease) + '</Application>')
			IF !EMPTY(this.CompanyName)
				FWRITE(lhFile, '<Company>' + this.CompanyName + '</Company>')
			ENDIF
			FWRITE(lhFile, '</Properties>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		*-*	Save the core XML
			lcFileName = tcPath + "docProps\core.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<cp:coreProperties ')
			FWRITE(lhFile, 'xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ')
			FWRITE(lhFile, 'xmlns:dc="http://purl.org/dc/elements/1.1/" ')
			FWRITE(lhFile, 'xmlns:dcterms="http://purl.org/dc/terms/" ')
			FWRITE(lhFile, 'xmlns:dcmitype="http://purl.org/dc/dcmitype/" ')
			FWRITE(lhFile, 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">')
			IF !EMPTY(this.Title)
				FWRITE(lhFile, '<dc:title>' + this.GetXMLString(this.Title) + '</dc:title>')
			ENDIF
			IF !EMPTY(this.Subject)
				FWRITE(lhFile, '<dc:subject>' + this.GetXMLString(this.Subject) + '</dc:subject>')
			ENDIF
			IF !EMPTY(this.CreatorName)
				FWRITE(lhFile, '<dc:creator>' + this.GetXMLString(this.CreatorName) + '</dc:creator>')
			ENDIF
			IF !EMPTY(this.UserName)
				FWRITE(lhFile, '<cp:lastModifiedBy>' + this.GetXMLString(this.UserName) + '</cp:lastModifiedBy>')
			ENDIF
			IF VERSION(5) < 900
				FWRITE(lhFile, '<dcterms:created xsi:type="dcterms:W3CDTF">' + this.TTOC3(DATETIME()) + 'Z</dcterms:created>')
				FWRITE(lhFile, '<dcterms:modified xsi:type="dcterms:W3CDTF">' + this.TTOC3(DATETIME()) + 'Z</dcterms:modified>')
			ELSE
				FWRITE(lhFile, '<dcterms:created xsi:type="dcterms:W3CDTF">' + TTOC(DATETIME(), 3) + 'Z</dcterms:created>')
				FWRITE(lhFile, '<dcterms:modified xsi:type="dcterms:W3CDTF">' + TTOC(DATETIME(), 3) + 'Z</dcterms:modified>')
			ENDIF
			FWRITE(lhFile, '</cp:coreProperties>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		*-*	Write the workbook XML
			lcFileName = tcPath + "xl\workbook.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile =< 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ LF)
			FWRITE(lhFile, '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">')
			FWRITE(lhFile, '<sheets>')
			FOR lnSheet=1 TO tnNumSheets
				IF EMPTY(taSheetNames[lnSheet])
					lcSheetName = "Sheet" + TRANSFORM(lnSheet)
				ELSE
					lcSheetName = taSheetNames[lnSheet]
				ENDIF
				FWRITE(lhFile, '<sheet name="' + lcSheetName + '" sheetId="' + TRANSFORM(lnSheet) + '" r:id="rId' + TRANSFORM(lnSheet+1) + '"/>')
			ENDFOR
			FWRITE(lhFile, '</sheets>')
			FWRITE(lhFile, '<calcPr calcId="0"/>')
			FWRITE(lhFile, '</workbook>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		CATCH TO loException
			SET STEP ON
			FCLOSE(lhFile)
			this.ErrorLevelId = 18
			RAISEEVENT(this, "OnShowErrorMessage", 18, "Unable to create workbook supporting XMLs")
		ENDTRY
		RETURN ISNULL(loException)
	ENDPROC

	PROTECTED PROCEDURE WriteDrawingDefinitionXML		&& Writes the drawing definition XML
		LPARAMETERS tnWB, tnSheet, tcPath
		LOCAL llSuccess, loException, lcFileName, lhDwgFile
		loException = .NULL.
		llSuccess   = True
		SELECT * FROM xl_relationships WHERE workbook = tnWB AND sheet = tnSheet AND relsrc = RELS_SRC_DRAWING INTO CURSOR xl_tempdwg
		IF _TALLY > 0
			TRY
		*-*		Write the drawing relationships and positioning file
				lcFileName = tcPath + "xl\drawings\drawing" + TRANSFORM(tnSheet) + ".xml"
				lhDwgFile = FCREATE(lcFileName)
				IF lhDwgFile < 0
					THROW
				ENDIF
		
				FWRITE(lhDwgFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
				FWRITE(lhDwgFile, '<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">')
		
				SELECT xl_tempdwg
				SCAN
					IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_tempdwg.relid), "xl_images", "relid")
						DO CASE
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_ABS
								FWRITE(lhDwgFile, '<xdr:absoluteAnchor>')
								FWRITE(lhDwgFile, '<xdr:ext x="' + TRANSFORM(xl_images.begcol) + '" y="' + TRANSFORM(xl_images.begrow) + '"/>')
								FWRITE(lhDwgFile, '<xdr:ext cx="' + TRANSFORM(xl_images.cx) + '" cy="' + TRANSFORM(xl_images.cy) + '"/>')
			
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_ONE
								FWRITE(lhDwgFile, '<xdr:oneCellAnchor>')
								FWRITE(lhDwgFile, '<xdr:from>')
								FWRITE(lhDwgFile, '<xdr:col>' + TRANSFORM(xl_images.begcol) + '</xdr:col>')
								FWRITE(lhDwgFile, '<xdr:colOff>' + TRANSFORM(xl_images.begcoloff) + '</xdr:colOff>')
								FWRITE(lhDwgFile, '<xdr:row>' + TRANSFORM(xl_images.begrow) + '</xdr:row>')
								FWRITE(lhDwgFile, '<xdr:rowOff>' + TRANSFORM(xl_images.begrowoff) + '</xdr:rowOff>')
								FWRITE(lhDwgFile, '</xdr:from>')
								FWRITE(lhDwgFile, '<xdr:ext cx="' + TRANSFORM(xl_images.cx) + '" cy="' + TRANSFORM(xl_images.cy) + '"/>')
			
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_TWO
								FWRITE(lhDwgFile, '<xdr:twoCellAnchor editAs="' + ALLTRIM(xl_images.imgmove) + '">')
								FWRITE(lhDwgFile, '<xdr:from>')
								FWRITE(lhDwgFile, '<xdr:col>' + TRANSFORM(xl_images.begcol) + '</xdr:col>')
								FWRITE(lhDwgFile, '<xdr:colOff>' + TRANSFORM(xl_images.begcoloff) + '</xdr:colOff>')
								FWRITE(lhDwgFile, '<xdr:row>' + TRANSFORM(xl_images.begrow) + '</xdr:row>')
								FWRITE(lhDwgFile, '<xdr:rowOff>' + TRANSFORM(xl_images.begrowoff) + '</xdr:rowOff>')
								FWRITE(lhDwgFile, '</xdr:from>')
								FWRITE(lhDwgFile, '<xdr:to>')
								FWRITE(lhDwgFile, '<xdr:col>' + TRANSFORM(xl_images.endcol) + '</xdr:col>')
								FWRITE(lhDwgFile, '<xdr:colOff>' + TRANSFORM(xl_images.endcoloff) + '</xdr:colOff>')
								FWRITE(lhDwgFile, '<xdr:row>' + TRANSFORM(xl_images.endrow) + '</xdr:row>')
								FWRITE(lhDwgFile, '<xdr:rowOff>' + TRANSFORM(xl_images.endrowoff) + '</xdr:rowOff>')
								FWRITE(lhDwgFile, '</xdr:to>')
						ENDCASE
						FWRITE(lhDwgFile, '<xdr:pic>')
						FWRITE(lhDwgFile, '<xdr:nvPicPr>')
						FWRITE(lhDwgFile, '<xdr:cNvPr id="' + TRANSFORM(xl_tempdwg.globalid) + '" name="' + JUSTSTEM(ALLTRIM(xl_tempdwg.target)) + '"/>')
						FWRITE(lhDwgFile, '<xdr:cNvPicPr>')
						FWRITE(lhDwgFile, '<a:picLocks noChangeAspect="1"/>')
						FWRITE(lhDwgFile, '</xdr:cNvPicPr>')
						FWRITE(lhDwgFile, '</xdr:nvPicPr>')
		
						FWRITE(lhDwgFile, '<xdr:blipFill>')
						FWRITE(lhDwgFile, '<a:blip xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:embed="rId' + TRANSFORM(xl_tempdwg.relid) + '">')
						FWRITE(lhDwgFile, '<a:extLst>')
						FWRITE(lhDwgFile, '<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">')
						FWRITE(lhDwgFile, '<a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>')
						FWRITE(lhDwgFile, '</a:ext>')
						FWRITE(lhDwgFile, '</a:extLst>')
						FWRITE(lhDwgFile, '</a:blip>')
						FWRITE(lhDwgFile, '<a:stretch>')
						FWRITE(lhDwgFile, '<a:fillRect/>')
						FWRITE(lhDwgFile, '</a:stretch>')
						FWRITE(lhDwgFile, '</xdr:blipFill>')
		
					    FWRITE(lhDwgFile, '<xdr:spPr>')
					    FWRITE(lhDwgFile, '<a:xfrm>')
					    FWRITE(lhDwgFile, '<a:off x="0" y="0"/>')
					    FWRITE(lhDwgFile, '<a:ext cx="' + TRANSFORM(xl_images.cx) + '" cy="' + TRANSFORM(xl_images.cy) + '"/>')
					    FWRITE(lhDwgFile, '</a:xfrm>')
					    FWRITE(lhDwgFile, '<a:prstGeom prst="rect">')
					    FWRITE(lhDwgFile, '<a:avLst/>')
					    FWRITE(lhDwgFile, '</a:prstGeom>')
					    FWRITE(lhDwgFile, '</xdr:spPr>')
		
						FWRITE(lhDwgFile, '</xdr:pic>')
						FWRITE(lhDwgFile, '<xdr:clientData/>')
						DO CASE
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_ABS
								FWRITE(lhDwgFile, '</xdr:absoluteAnchor>')
			
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_ONE
								FWRITE(lhDwgFile, '</xdr:oneCellAnchor>')
			
							CASE xl_images.anchor = IMAGE_ANCHOR_TYPE_TWO
								FWRITE(lhDwgFile, '</xdr:twoCellAnchor>')
						ENDCASE
					ENDIF
				ENDSCAN
		
				FWRITE(lhDwgFile, '</xdr:wsDr>')
		
			CATCH TO loException
				SET STEP ON
				llSuccess = False
				this.ErrorLevelId = 16
				RAISEEVENT(this, "OnShowErrorMessage", 16, "Unable to create sheet" + TRANSFORM(tnSheet))
		
			FINALLY
				IF ISNULL(loException)
					FCLOSE(lhDwgFile)
					this.SaveAsUTF8(lcFileName)
				ENDIF
			ENDTRY
		ENDIF
		USE IN SELECT("xl_tempdwg")
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteImageRelationshipsXML		&& Writes the image relationship (.rels) xml file
		LPARAMETERS tnWB, tnSheet, tcPath
		LOCAL llSuccess, loException, lcFileName, lhDwgRels, lcBaseType, lcTgtFile
		llSuccess   = True
		loException = .NULL.
		lcBaseType  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
		SELECT * FROM xl_relationships WHERE workbook = tnWB AND sheet = tnSheet AND relsrc = RELS_SRC_DRAWING INTO CURSOR xl_tempimg
		IF _TALLY > 0
			TRY
			*-*	Write the image relationships file
				lcFileName = tcPath + "xl\drawings\_rels\drawing" + TRANSFORM(tnSheet) + ".xml.rels"
				lhDwgRels = FCREATE(lcFileName)
				IF lhDwgRels < 0
					THROW
				ENDIF
			
				FWRITE(lhDwgRels, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
				FWRITE(lhDwgRels, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
				SELECT xl_tempimg
				SCAN
					TRY
						lcTgtFile = tcPath + "xl/media/image" + TRANSFORM(xl_tempimg.globalid) + "." + JUSTEXT(ALLTRIM(xl_tempimg.target))
						this.ConvertStringToFile(ALLTRIM(xl_tempimg.filecontent), lcTgtFile, 14)
			
					CATCH TO loException
						THROW loException.Message
					ENDTRY
					IF ISNULL(loException)
						FWRITE(lhDwgRels, '<Relationship Id="rId' + TRANSFORM(xl_tempimg.relid) + '"')
						FWRITE(lhDwgRels, ' Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"')
						FWRITE(lhDwgRels, ' Target="../media/' + JUSTFNAME(lcTgtFile) + '"/>')
					ENDIF
				ENDSCAN
				FWRITE(lhDwgRels, '</Relationships>')
			
			CATCH TO loException
				SET STEP ON
				llSuccess = False
				this.ErrorLevelId = 16
				RAISEEVENT(this, "OnShowErrorMessage", 16, "Unable to create sheet" + TRANSFORM(tnSheet))
			
			FINALLY
				IF ISNULL(loException)
					FCLOSE(lhDwgRels)
					this.SaveAsUTF8(lcFileName)
				ENDIF
			ENDTRY
		ENDIF
		USE IN SELECT("xl_tempimg")
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteSheetRelationshipsXML		&& Writes the sheet relationships xml file
		LPARAMETERS tnWB, tnSheet, tcPath
		LOCAL lcBaseType, llSuccess, lhShRels, loException, lcRelsFileName
		LOCAL ARRAY laRecord[1]
		llSuccess   = True
		loException = .NULL.
		lcBaseType  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/"
		SELECT * FROM xl_relationships WHERE workbook = tnWB AND sheet = tnSheet AND relsrc = RELS_SRC_SHEET INTO CURSOR xl_tempsh
		IF _TALLY > 0
			TRY
		*-*		Write the sheet relationships file
				lcRelsFileName = tcPath + "xl\worksheets\_rels\sheet" + TRANSFORM(tnSheet) + ".xml.rels"
				lhShRels = FCREATE(lcRelsFileName)
				IF lhShRels < 0
					THROW
				ENDIF
		
				FWRITE(lhShRels, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
				FWRITE(lhShRels, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
		
				SELECT xl_tempsh
				SCAN
					FWRITE(lhShRels, '<Relationship Id="rId' + TRANSFORM(xl_tempsh.relid) + '"')
					FWRITE(lhShRels, ' Type="' + lcBaseType + this.GetRelationTypeText(xl_tempsh.reltype) + '"')
					FWRITE(lhShRels, ' Target="' + this.GetXMLString(ALLTRIM(xl_tempsh.target)) + '"')
					IF xl_tempsh.external
						FWRITE(lhShRels, ' TargetMode="External"')
					ENDIF
					FWRITE(lhShRels, '/>')
				ENDSCAN
		
				FWRITE(lhShRels, '</Relationships>')
		
			CATCH TO loException
				SET STEP ON
				llSuccess = False
				this.ErrorLevelId = 16
				RAISEEVENT(this, "OnShowErrorMessage", 16, "Unable to create sheet" + TRANSFORM(tnSheet) + " Relationships file")
			
			FINALLY
				IF ISNULL(loException)
					FCLOSE(lhShRels)
					this.SaveAsUTF8(lcRelsFileName)
				ENDIF
			ENDTRY
			llSuccess = this.WriteDrawingDefinitionXML(tnWB, tnSheet, tcPath)
			SELECT relid FROM xl_relationships WHERE workbook = tnWB AND sheet = tnSheet AND reltype = RELS_TYPE_IMAGE INTO ARRAY laRecord
			IF llSuccess .AND. VARTYPE(laRecord[1]) = "N"
				llSuccess = this.WriteImageRelationshipsXML(tnWB, tnSheet, tcPath)
			ENDIF
		ELSE
			llSuccess = True
		ENDIF
		USE IN SELECT("xl_tempsh")
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteSheetXMLs		&& Writes the Sheet XML files
		LPARAMETERS tnWB, tnSheet, tcPath
		LOCAL llSuccess, lcAlias, lhShFile, lcRow, lnMinCol, lnWidth, lcDyDescent
		LOCAL lcRowDefHt, lcFileName, lcRelsFileName, lhRels, lcValidationText, lcSqRef, lnValidCnt, lcStyle
		LOCAL lnHeadRow, lcColumn, lcOddText, lcEvenText, lcSameText, lcHeaderText, lcFooterText, lcFirstHdrText
		LOCAL lcFirstFtrText, loException, lcRange, lnLastCol, lcCellFormula, lcMaxCol
		LOCAL ARRAY laRecord[1], laRelId[1], laMaxCol[1]
		*-*	Write the sheet XML
		lcAlias = ALIAS()
		*-*	Write the sheet
		loException = .NULL.
		TRY
			lcFileName = tcPath + "xl\worksheets\sheet" + TRANSFORM(tnSheet) + ".xml"
			lhShFile   = FCREATE(lcFileName)
			IF lhShFile <= 0
				THROW
			ENDIF
			lcDyDescent = ''
		*	lcDyDescent = ' x14ac:dyDescent="0.25"'                         && Uncomment to add back...
		
		*-*	Write sheet XML header information
			FWRITE(lhShFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhShFile, '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"')
			FWRITE(lhShFile, ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"')
			FWRITE(lhShFile, ' xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"')
			FWRITE(lhShFile, ' xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
		
		*-*	Write the tab color definition if set and sheet outline settings
			FWRITE(lhShFile, '<sheetPr codeName="Sheet' + TRANSFORM(tnSheet) + '">')
			DO CASE
				CASE xl_sheets.tabcolorndx > 0
					FWRITE(lhShFile, '<tabColor indexed="' + TRANSFORM(xl_sheets.tabcolorndx) + '"/>')
				
				CASE !EMPTY(xl_sheets.tabcolorrgb)
					FWRITE(lhShFile, '<tabColor rgb="' + xl_sheets.tabcolorrgb + '"/>')
			ENDCASE
		*	FWRITE(lhShFile, '<pageSetUpPr fitToPage="' + TRANSFORM(xl_sheets.fittopage) + '"/>')   && If uncommented; sheet will be invalid by Excel
			FWRITE(lhShFile, '<outlinePr summaryBelow="' + TRANSFORM(xl_sheets.grpsumbelow) + '" summaryRight="' + TRANSFORM(xl_sheets.grpsumright) + '"/>')
			FWRITE(lhShFile, '</sheetPr>')
		
		*-*	Verify cells have been written
			SELECT xl_cells
			LOCATE FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSheet .AND. !xl_cells.celldeleted
			IF FOUND()
		*-*		Write the sheet view information which includes the freeze pane information
				SELECT cel.sheet, MAX(cel.cellcol), MAX(cel.cellrow) ;
					FROM xl_cells AS cel ;
					WHERE cel.workbook = tnWB ;
						AND cel.sheet = tnSheet ;
						AND cel.celldeleted = False ;
					GROUP BY 1 ;
					INTO ARRAY laRecord
		
				FWRITE(lhShFile, '<dimension ref="A1:' + this.ColumnIndexToAscii(NVL(laRecord[1, 2], 1)) + TRANSFORM(NVL(laRecord[1, 3], 1)) + '"/>')
				FWRITE(lhShFile, '<sheetViews><sheetView workbookViewId="0"')
				IF xl_sheets.gridlines = 0     &&  default behaviour is to show gridlines; show grid lines enhancement suggested by Doug Hennig
					FWRITE(lhShFile, ' showGridLines="' + TRANSFORM(xl_sheets.gridlines) + '"')
				ENDIF
				IF xl_sheets.tabselctd > 0
					FWRITE(lhShFile, ' tabSelected="' + TRANSFORM(xl_sheets.tabselctd) + '"')
				ENDIF
				IF xl_sheets.zoomscale > 0
					FWRITE(lhShFile, ' zoomScale="' + TRANSFORM(xl_sheets.zoomscale) + '" zoomScaleNormal="' + TRANSFORM(xl_sheets.zoomscale) + '"')
				ENDIF
				FWRITE(lhShFile, '>')
				DO CASE
					CASE xl_sheets.ysplit > 0 .AND. xl_sheets.xsplit = 0      && Freeze top row(s) only
						lcRow = TRANSFORM(xl_sheets.ysplit+1)
						FWRITE(lhShFile, '<pane state="frozen" activePane="bottomLeft" topLeftCell="A' + lcRow + '" ySplit="' + TRANSFORM(xl_sheets.ysplit) + '"/>')
						FWRITE(lhShFile, '<selection activeCell="A' + lcRow + '" sqref="A' + lcRow + '" pane="bottomLeft"/>')
		
					CASE xl_sheets.ysplit = 0 .AND. xl_sheets.xsplit > 0      && Freeze side column(s) only
						lcColumn = this.ColumnIndexToAscii(xl_sheets.xsplit+1)
						FWRITE(lhShFile, '<pane state="frozen" activePane="bottomLeft" topLeftCell="' + lcColumn + '1" xSplit="' + TRANSFORM(xl_sheets.xsplit) + '"/>')
						FWRITE(lhShFile, '<selection activeCell="' + lcColumn + '1" sqref="' + lcColumn + '1" pane="topRight"/>')
		
					CASE xl_sheets.ysplit > 0 .AND. xl_sheets.xsplit > 0      && Freeze both top row(s) and side column(s)
						lcRow = TRANSFORM(xl_sheets.ysplit+1)
						lcColumn = this.ColumnIndexToAscii(xl_sheets.xsplit+1)
						FWRITE(lhShFile, '<pane state="frozen" activePane="bottomRight" topLeftCell="' + lcColumn + lcRow + ;
							'" ySplit="' + TRANSFORM(xl_sheets.ysplit) + '" xSplit="' + TRANSFORM(xl_sheets.xsplit) + '"/>')
						FWRITE(lhShFile, '<selection activeCell="' + this.ColumnIndexToAscii(xl_sheets.xsplit) + '1" sqref="' + this.ColumnIndexToAscii(xl_sheets.xsplit) + '1" pane="topRight"/>')
						FWRITE(lhShFile, '<selection activeCell="' + this.ColumnIndexToAscii(xl_sheets.xsplit) + lcRow + '" sqref="' + this.ColumnIndexToAscii(xl_sheets.xsplit) + lcRow + '" pane="bottomLeft"/>')
						FWRITE(lhShFile, '<selection activeCell="' + lcColumn + lcRow + '" sqref="' + lcColumn + lcRow + '" pane="bottomRight"/>')
		
					OTHERWISE
						IF EMPTY(xl_sheets.activcell)
							FWRITE(lhShFile, '<selection activeCell="A1" sqref="A1"/>')
						ELSE
							FWRITE(lhShFile, '<selection activeCell="' + ALLTRIM(xl_sheets.activcell) + '" sqref="' + ALLTRIM(xl_sheets.activcell) + '"/>')
						ENDIF
				ENDCASE
				FWRITE(lhShFile, '</sheetView></sheetViews>')
				FWRITE(lhShFile, '<sheetFormatPr defaultRowHeight="15"' + lcDyDescent + '/>')
		
		*-*		Write sheet Column widths
				SELECT xl_columns
				LOCATE FOR xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSheet
				IF FOUND()
					FWRITE(lhShFile, '<cols>')
					SET ORDER TO TAG col
					SET FILTER TO xl_columns.workbook = tnWB .AND. xl_columns.sheet = tnSheet
					GOTO TOP IN xl_columns
					lcColMin   = TRANSFORM(xl_columns.col)
					lnColLevel = xl_columns.collevel
					lnColHiddn = xl_columns.hidden
					lnColWidth = xl_columns.width
					DO WHILE .NOT. EOF('xl_columns')
						lcColMax = TRANSFORM(xl_columns.col)
						SKIP 1 IN xl_columns
						IF xl_columns.collevel != lnColLevel .OR. xl_columns.hidden != lnColHiddn .OR. xl_columns.width != lnColWidth
							FWRITE(lhShFile, '<col min="' + lcColMin + '" max="' + lcColMax + '"')
							DO CASE
								CASE lnColWidth > -1.00 .AND. lnColLevel = 0 .AND. lnColHiddn = 0
									FWRITE(lhShFile, ' width="' + TRANSFORM(lnColWidth+0.7109375) + '" customWidth="1"/>')
			
								CASE lnColWidth > -1.00 .AND. lnColLevel = 0 .AND. lnColHiddn = 1
									FWRITE(lhShFile, ' width="' + TRANSFORM(lnColWidth+0.7109375) + '" hidden="1" customWidth="1"/>')
			
								CASE lnColWidth = -1.00 .AND. lnColLevel > 0
									FWRITE(lhShFile, ' outlineLevel="' + TRANSFORM(lnColLevel) + '"/>')
			
								CASE lnColWidth > -1.00 .AND. lnColLevel > 0
									FWRITE(lhShFile, ' width="' + TRANSFORM(lnColWidth+0.7109375) + '" customWidth="1" outlineLevel="' + TRANSFORM(lnColLevel) + '"/>')
			
								OTHERWISE
									FWRITE(lhShFile, '/>')
							ENDCASE
							lcColMin   = TRANSFORM(xl_columns.col)
							lnColLevel = xl_columns.collevel
							lnColHiddn = xl_columns.hidden
							lnColWidth = xl_columns.width
						ENDIF
					ENDDO
					SET FILTER TO
					SET ORDER TO
					FWRITE(lhShFile, '</cols>')
				ENDIF
		
		*-*		Write sheet cell data values by row/column
				FWRITE(lhShFile, '<sheetData>')
				SELECT xl_cells
				SET ORDER TO TAG cellindex
				SELECT xl_rows
				SET ORDER TO TAG row
				SCAN FOR xl_rows.workbook = tnWB .AND. xl_rows.sheet = tnSheet .AND. xl_rows.row > 0
					FWRITE(lhShFile, '<row r="' + TRANSFORM(xl_rows.row) + '" spans="1:' + TRANSFORM(xl_rows.maxcol) + '"')
					IF xl_rows.hidden > 0
						FWRITE(lhShFile, ' hidden="1"')
					ENDIF
					DO CASE
						CASE xl_rows.height > -1.00 .AND. xl_rows.rowlevel = 0
							FWRITE(lhShFile, ' ht="' + TRANSFORM(xl_rows.height) + '" customHeight="1">')
		
						CASE xl_rows.height = -1.00 .AND. xl_rows.rowlevel > 0
							FWRITE(lhShFile, ' outlineLevel="' + TRANSFORM(xl_rows.rowlevel) + '" collapsed="1">')
		
						CASE xl_rows.height > -1.00 .AND. xl_rows.rowlevel > 0
							FWRITE(lhShFile, ' ht="' + TRANSFORM(xl_rows.height) + '" customHeight="1" outlineLevel="' + TRANSFORM(xl_rows.rowlevel) + '" collapsed="1">')
		
						OTHERWISE
							IF this.AutoHeight                                              && Suggestion for Autoheight from Cesar Chalom
								lcRowDefHt = ' ht="' + TRANSFORM(this.GetRowMaxFontSize(tnWB, tnSheet, xl_rows.row) * 1.4) + '"'
							ELSE
								lcRowDefHt = ' ht="30"'
							ENDIF
							FWRITE(lhShFile, lcRowDefHt + '>')
					ENDCASE
		
					SELECT xl_cells
					SCAN FOR xl_cells.workbook = tnWB .AND. xl_cells.sheet = tnSheet .AND. xl_cells.cellrow = xl_rows.row
						IF xl_cells.cellxfs > 0
							lcStyle = ' s="' + TRANSFORM(xl_cells.cellxfs) + '"'
						ELSE
							lcStyle = ''
						ENDIF
						DO CASE
							CASE xl_cells.datatype = DATA_TYPE_TIME
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_DATE
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_DATETIME
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_LOGICAL
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle)
								IF xl_cells.stringid > -1
									FWRITE(lhShFile, ' t="s"><v>' + TRANSFORM(xl_cells.stringid) + '</v></c>')
								ELSE
									FWRITE(lhShFile, ' t="inlineStr"><is><t>' + this.GetXMLString(ALLTRIM(xl_cells.cellvalue)) + '</t></is></c>')
								ENDIF
		
							CASE xl_cells.datatype = DATA_TYPE_CHAR
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle)
								IF xl_cells.stringid > -1
									FWRITE(lhShFile, ' t="s"><v>' + TRANSFORM(xl_cells.stringid) + '</v></c>')
								ELSE
									FWRITE(lhShFile, ' t="inlineStr"><is><t>' + this.GetXMLString(ALLTRIM(xl_cells.cellvalue)) + '</t></is></c>')
								ENDIF
		
							CASE xl_cells.datatype = DATA_TYPE_INT
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_FLOAT
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_CURRENCY
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_PERCENT
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v></c>')
		
							CASE xl_cells.datatype = DATA_TYPE_FORMULA
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + ' t="str"' + '>')
								IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_cells.cellrow)+BINTOC(xl_cells.cellcol), "xl_formulas", "cell")
									lcCellFormula = this.GetXMLString(ALLTRIM(xl_formulas.cellformula))
									DO CASE
										CASE xl_formulas.formulatype = "shared"
											FWRITE(lhShFile, '<f t="shared" ref="' + ALLTRIM(xl_formulas.ref) + '" si="' + TRANSFORM(xl_formulas.si) + '">' + lcCellFormula + '</f>')
			
										CASE xl_formulas.formulatype = "array"
											FWRITE(lhShFile, '<f t="array" ref="' + ALLTRIM(xl_formulas.ref) + '">' + lcCellFormula + '</f>')
			
										CASE xl_formulas.formulatype = "dataTable"
			
										OTHERWISE
											FWRITE(lhShFile, '<f>' + lcCellFormula + '</f>')
									ENDCASE
								ELSE
									DO CASE
										CASE xl_cells.formulatype = "shared"
											FWRITE(lhShFile, '<f t="shared" si="' + TRANSFORM(xl_cells.formulasi) + '"/>')
										
										CASE xl_cells.formulatype = "array"
		
										CASE xl_cells.formulatype = "dataTable"
									ENDCASE
								ENDIF
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v>')
								FWRITE(lhShFile, '</c>')
		
							CASE xl_cells.datatype = DATA_TYPE_TEXT
								FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '>')
								FWRITE(lhShFile, '<v>' + ALLTRIM(xl_cells.cellvalue) + '</v>')
								FWRITE(lhShFile, '</c>')
		
							CASE xl_cells.datatype = DATA_TYPE_NONE
								IF !EMPTY(lcStyle)
									FWRITE(lhShFile, '<c r="' + this.ColumnIndexToAscii(xl_cells.cellcol) + TRANSFORM(xl_cells.cellrow) + '"' + lcStyle + '></c>')
								ENDIF
						ENDCASE
					ENDSCAN
					FWRITE(lhShFile, '</row>')
				ENDSCAN
				FWRITE(lhShFile, '</sheetData>')
				SELECT xl_cells
				SET ORDER TO
				SELECT xl_rows
				SET ORDER TO
			ELSE
		*-*		No cell data has been created/written
				FWRITE(lhShFile, '<dimension ref="A1"/>')
				FWRITE(lhShFile, '<sheetViews>')
				FWRITE(lhShFile, '<sheetView workbookViewId="0"/>')
				FWRITE(lhShFile, '</sheetViews>')
				FWRITE(lhShFile, '<sheetFormatPr defaultRowHeight="14.4"/>')
				FWRITE(lhShFile, '<sheetData/>')
			ENDIF
		
		*-*	Write sheet protection data
			IF xl_sheets.protection
				FWRITE(lhShFile, '<sheetProtection password="' + ALLTRIM(xl_sheets.password) + '"')
				IF !EMPTY(xl_sheets.algorithm)
					FWRITE(lhShFile, ' algorithmName="' + ALLTRIM(xl_sheets.algorithm) + '"')
				ENDIF
				FWRITE(lhShFile, ' sheet="1"')
				IF xl_sheets.autofilter
					FWRITE(lhShFile, ' autoFilter="1"')
				ELSE
					FWRITE(lhShFile, ' autoFilter="0"')
				ENDIF
				IF xl_sheets.deletecol
					FWRITE(lhShFile, ' deleteColumns="1"')
				ELSE
					FWRITE(lhShFile, ' deleteColumns="0"')
				ENDIF
				IF xl_sheets.deleterow
					FWRITE(lhShFile, ' deleteRows="1"')
				ELSE
					FWRITE(lhShFile, ' deleteRows="0"')
				ENDIF
				IF xl_sheets.formatcell
					FWRITE(lhShFile, ' formatCells="1"')
				ELSE
					FWRITE(lhShFile, ' formatCells="0"')
				ENDIF
				IF xl_sheets.formatcol
					FWRITE(lhShFile, ' formatColumns="1"')
				ELSE
					FWRITE(lhShFile, ' formatColumns="0"')
				ENDIF
				IF xl_sheets.formatrow
					FWRITE(lhShFile, ' formatRows="1"')
				ELSE
					FWRITE(lhShFile, ' formatRows="0"')
				ENDIF
				IF xl_sheets.insertcol
					FWRITE(lhShFile, ' insertColumns="1"')
				ELSE
					FWRITE(lhShFile, ' insertColumns="0"')
				ENDIF
				IF xl_sheets.insertrow
					FWRITE(lhShFile, ' insertRows="1"')
				ELSE
					FWRITE(lhShFile, ' insertRows="0"')
				ENDIF
				IF xl_sheets.inserthyper
					FWRITE(lhShFile, ' insertHyperlinks="1"')
				ELSE
					FWRITE(lhShFile, ' insertHyperlinks="0"')
				ENDIF
				IF xl_sheets.pivottbl
					FWRITE(lhShFile, ' pivotTables="1"')
				ELSE
					FWRITE(lhShFile, ' pivotTables="0"')
				ENDIF
				IF xl_sheets.sellocked
					FWRITE(lhShFile, ' selectLockedCells="1"')
				ELSE
					FWRITE(lhShFile, ' selectLockedCells="0"')
				ENDIF
				IF xl_sheets.selunlocked
					FWRITE(lhShFile, ' selectUnlockedCells="1"')
				ELSE
					FWRITE(lhShFile, ' selectUnlockedCells="0"')
				ENDIF
				IF xl_sheets.sort
					FWRITE(lhShFile, ' sort="1"')
				ELSE
					FWRITE(lhShFile, ' sort="0"')
				ENDIF
				IF xl_sheets.objects
					FWRITE(lhShFile, ' objects="1"')
				ELSE
					FWRITE(lhShFile, ' objects="0"')
				ENDIF
				IF xl_sheets.scenarios
					FWRITE(lhShFile, ' scenarios="1"')
				ELSE
					FWRITE(lhShFile, ' scenarios="0"')
				ENDIF
				FWRITE(lhShFile, '/>')
			ENDIF
		
		*-*	Write the column filter if assigned
			IF xl_sheets.addfilter
				lcRange = this.ColumnIndexToAscii(xl_sheets.filtbegcol) + "1:" + this.ColumnIndexToAscii(xl_sheets.filtendcol) + TRANSFORM(NVL(laRecord[1, 3], 1))
				FWRITE(lhShFile, '<autoFilter ref="' + lcRange + '"')
				IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_filters", "colfilter")
					FWRITE(lhShFile, '>')
					SELECT col, filtoper, filtvalue FROM xl_filters WHERE workbook = tnWB AND sheet = tnSheet ORDER BY col INTO CURSOR tmp_filter
					SELECT DISTINCT col, andoperator FROM xl_filters WHERE workbook = tnWB AND sheet = tnSheet ORDER BY col INTO CURSOR tmp_columns
		
					SELECT tmp_columns
					SCAN
						FWRITE(lhShFile, '<filterColumn colId="' + TRANSFORM(tmp_columns.col - xl_sheets.filtbegcol) + '">')
						FWRITE(lhShFile, '<customFilters' + IIF(tmp_columns.andoperator, ' and="1">', '>'))
						SELECT tmp_filter
						SCAN FOR tmp_filter.col = tmp_columns.col
							FWRITE(lhShFile, '<customFilter operator="' + ALLTRIM(tmp_filter.filtoper) + '" val="' + ALLTRIM(tmp_filter.filtvalue) + '"/>')
						ENDSCAN
						FWRITE(lhShFile, '</customFilters>')
						FWRITE(lhShFile, '</filterColumn>')
					ENDSCAN
					USE IN SELECT('tmp_filter')
					USE IN SELECT('tmp_columns')
		
					FWRITE(lhShFile, '</autoFilter>')
				ELSE
					FWRITE(lhShFile, '/>')
				ENDIF
			ENDIF
		
		*-*	Write sheet merge data
			SELECT mrg.begrow, mrg.begcol, mrg.endrow, mrg.endcol FROM xl_mergecells AS mrg ;
				WHERE mrg.workbook = tnWB ;
					AND mrg.sheet = tnSheet ;
				ORDER BY mrg.begrow ;
				INTO CURSOR t_merge
		
			IF _TALLY > 0
				FWRITE(lhShFile, '<mergeCells>')
				SELECT t_merge
				SCAN
					FWRITE(lhShFile, '<mergeCell ref="' + this.ColumnIndexToAscii(t_merge.begcol) + TRANSFORM(t_merge.begrow) + ':' + ;
						   this.ColumnIndexToAscii(t_merge.endcol) + TRANSFORM(t_merge.endrow) + '"/>')
				ENDSCAN
				FWRITE(lhShFile, '</mergeCells>')
			ENDIF
			USE IN t_merge
		
		*-*	Write the cell validation data
			SELECT xl_validation
			COUNT FOR xl_validation.workbook = tnWB .AND. xl_validation.sheet = tnSheet .AND. !DELETED() TO lnValidCnt
			IF lnValidCnt > 0
				FWRITE(lhShFile, '<dataValidations count="' + TRANSFORM(lnValidCnt) + '">')
				SCAN
					SELECT xl.cellrow, xl.cellcol FROM xl_cells AS xl ;
						WHERE BINTOC(xl.workbook)+BINTOC(xl.sheet)+BINTOC(xl.validndx) = BINTOC(tnWB)+BINTOC(tnSheet)+BINTOC(xl_validation.validndx) ;
						ORDER BY xl.cellrow, xl.cellcol ;
						INTO CURSOR t_celllist
					SELECT t_celllist
					lcSqRef = ""
					SCAN
						lcSqRef = lcSqRef + this.ColumnIndexToAscii(t_celllist.cellcol) + TRANSFORM(t_celllist.cellrow) + " "
					ENDSCAN
					lcSqRef = ALLTRIM(lcSqRef)
					DO CASE
						CASE xl_validation.vtype = NONE_VALID_TYPE
							lcValidationText = 'type="none"'
		
						CASE xl_validation.vtype = WHOLE_VALID_TYPE
							lcValidationText = 'type="whole"'
		
						CASE xl_validation.vtype = DECIMAL_VALID_TYPE
							lcValidationText = 'type="decimal"'
		
						CASE xl_validation.vtype = LIST_VALID_TYPE
							lcValidationText = 'type="list"'
		
						CASE xl_validation.vtype = DATE_VALID_TYPE
							lcValidationText = 'type="date"'
		
						CASE xl_validation.vtype = TIME_VALID_TYPE
							lcValidationText = 'type="time"'
		
						CASE xl_validation.vtype = TXTLEN_VALID_TYPE
							lcValidationText = 'type="textLength"'
		
						CASE xl_validation.vtype = CUSTOM_VALID_TYPE
							lcValidationText = 'type="custom"'
		
						OTHERWISE
							lcValidationText = 'type="none"'
					ENDCASE
					IF xl_validation.vstyle > 0
						DO CASE
							CASE xl_validation.vstyle = STOP_VALID_STYLE
								lcValidationText = lcValidationText + ' errorStyle="stop"'
		
							CASE xl_validation.vstyle = WARN_VALID_STYLE
								lcValidationText = lcValidationText + ' errorStyle="warning"'
		
							CASE xl_validation.vstyle = INFO_VALID_STYLE
								lcValidationText = lcValidationText + ' errorStyle="information"'
						ENDCASE
					ENDIF
					IF xl_validation.voperator > 0
						DO CASE
							CASE xl_validation.voperator = BETWEEN_VALID_OPER
								lcValidationText = lcValidationText + ' operator="between"'
		
							CASE xl_validation.voperator = NOTBETW_VALID_OPER
								lcValidationText = lcValidationText + ' operator="notBetween"'
		
							CASE xl_validation.voperator = EQUAL_VALID_OPER
								lcValidationText = lcValidationText + ' operator="equal"'
		
							CASE xl_validation.voperator = NOTEQUAL_VALID_OPER
								lcValidationText = lcValidationText + ' operator="notEqual"'
		
							CASE xl_validation.voperator = LESSTHAN_VALID_OPER
								lcValidationText = lcValidationText + ' operator="lessThan"'
		
							CASE xl_validation.voperator = LESSOREQUAL_VALID_OPER
								lcValidationText = lcValidationText + ' operator="lessThanOrEqual"'
		
							CASE xl_validation.voperator = GREATTHAN_VALID_OPER
								lcValidationText = lcValidationText + ' operator="greaterThan"'
		
							CASE xl_validation.voperator = GREATOREQUAL_VALID_OPER
								lcValidationText = lcValidationText + ' operator="greaterThanOrEqual"'
						ENDCASE
					ENDIF
					lcValidationText = lcValidationText + ' allowBlank="' + IIF(xl_validation.allowblank, '1', '0') + '"'
					lcValidationText = lcValidationText + ' showInputMessage="' + IIF(xl_validation.showinpmsg, '1', '0') + '"'
					lcValidationText = lcValidationText + ' showErrorMessage="' + IIF(xl_validation.showerrmsg, '1', '0') + '"'
					IF !EMPTY(xl_validation.errtitle)
						lcValidationText = lcValidationText + ' errorTitle="' + ALLTRIM(xl_validation.errtitle) + '"'
					ENDIF
					IF !EMPTY(xl_validation.errmsg)
						lcValidationText = lcValidationText + ' error="' + ALLTRIM(xl_validation.errmsg) + '"'
					ENDIF
					IF !EMPTY(xl_validation.vprompt)
						lcValidationText = lcValidationText + ' prompt="' + ALLTRIM(xl_validation.vprompt) + '"'
					ENDIF
					FWRITE(lhShFile, '<dataValidation ' + lcValidationText + ' sqref="' + lcSqRef + '">')
					IF xl_validation.formula
						FWRITE(lhShFile, '<formula1>' + ALLTRIM(xl_validation.formula1) + '</formula1>')
						IF !EMPTY(xl_validation.formula2)
							FWRITE(lhShFile, '<formula2>' + ALLTRIM(xl_validation.formula2) + '</formula2>')
						ENDIF
					ELSE
						FWRITE(lhShFile, '<formula1>"' + ALLTRIM(xl_validation.formula1) + '"</formula1>')
						IF !EMPTY(xl_validation.formula2)
							FWRITE(lhShFile, '<formula2>"' + ALLTRIM(xl_validation.formula2) + '"</formula2>')
						ENDIF
					ENDIF
					FWRITE(lhShFile, '</dataValidation>')
				ENDSCAN
				USE IN SELECT('t_celllist')
				FWRITE(lhShFile, '</dataValidations>')
			ENDIF
		
		*-*	Write sheet hyperlink data
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_hyperlinks", "sheet")
				FWRITE(lhShFile, '<hyperlinks>')
				SELECT xl_hyperlinks
				SCAN FOR workbook = tnWB .AND. sheet = tnSheet
					FWRITE(lhShFile, '<hyperlink ref="')
					DO CASE
						CASE xl_hyperlinks.begrow = xl_hyperlinks.endrow .AND. xl_hyperlinks.begcol = xl_hyperlinks.endcol
							FWRITE(lhShFile, this.ColumnIndexToAscii(xl_hyperlinks.begcol) + TRANSFORM(xl_hyperlinks.begrow) + '"')
						
						OTHERWISE
							FWRITE(lhShFile, this.ColumnIndexToAscii(xl_hyperlinks.begcol) + TRANSFORM(xl_hyperlinks.begrow) + ':')
							FWRITE(lhShFile, this.ColumnIndexToAscii(xl_hyperlinks.endcol) + TRANSFORM(xl_hyperlinks.endrow) + '"')
					ENDCASE
					IF xl_hyperlinks.targettype = HYPERLINK_TYPE_FILE
						FWRITE(lhShFile, ' r:id="rId' + TRANSFORM(xl_hyperlinks.relid) + '"/>')
					ELSE
						lcSheetName = this.GetSheetName(tnWB, xl_hyperlinks.tgtsheet)
						IF EMPTY(lcSheetName)
							lcTarget = "'Sheet" + TRANSFORM(xl_hyperlinks.tgtsheet) + "'!"
						ELSE
							lcTarget = "'" + lcSheetName + "'!"
						ENDIF
						lcTarget = lcTarget + this.ColumnIndexToAscii(xl_hyperlinks.tgtBegCol) + TRANSFORM(xl_hyperlinks.tgtBegRow)
						lcTarget = lcTarget + ":"
						lcTarget = lcTarget + this.ColumnIndexToAscii(xl_hyperlinks.tgtEndCol) + TRANSFORM(xl_hyperlinks.tgtEndRow)
						FWRITE(lhShFile, ' location="' + lcTarget + '" display="' +  this.GetXMLString(ALLTRIM(xl_hyperlinks.display)) + '"/>')
					ENDIF
				ENDSCAN
				FWRITE(lhShFile, '</hyperlinks>')
			ENDIF
		
		*-*	Write sheet print options
			IF xl_sheets.prnopthead > 0 .OR. xl_sheets.prnoptgrid > 0 .OR. xl_sheets.prnoptctrv > 0 .OR. xl_sheets.prnoptctrh > 0
				FWRITE(lhShFile, '<printOptions')
				IF xl_sheets.prnopthead > 0
					FWRITE(lhShFile, ' headings="' + TRANSFORM(xl_sheets.prnopthead) + '"')
				ENDIF
				IF xl_sheets.prnoptgrid > 0
					FWRITE(lhShFile, ' gridLines="' + TRANSFORM(xl_sheets.prnoptgrid) + '"')
				ENDIF
				IF xl_sheets.prnoptctrv > 0
					FWRITE(lhShFile, ' verticalCentered="' + TRANSFORM(xl_sheets.prnoptctrv) + '"')
				ENDIF
				IF xl_sheets.prnoptctrh > 0
					FWRITE(lhShFile, ' horizontalCentered="' + TRANSFORM(xl_sheets.prnoptctrh) + '"')
				ENDIF
				FWRITE(lhShFile, '/>')
			ENDIF
		
		*-*	Write sheet margin data
			FWRITE(lhShFile, '<pageMargins left="' + RTRIM(TRANSFORM(xl_sheets.mleft), 1, '0') + '" right="' + RTRIM(TRANSFORM(xl_sheets.mright), 1, '0') + ;
				'" top="' + RTRIM(TRANSFORM(xl_sheets.mtop), 1, '0') + '" bottom="' + RTRIM(TRANSFORM(xl_sheets.mbot), 1, '0') + ;
				'" header="' + RTRIM(TRANSFORM(xl_sheets.mheader), 1, '0') + '" footer="' + RTRIM(TRANSFORM(xl_sheets.mfooter), 1, '0') + '"/>')
		
		*-*	Write the sheet page print setup
			FWRITE(lhShFile, '<pageSetup orientation="' + IIF(xl_sheets.prnorient=PORTRAIT_PRINT_ORIENTATION, 'portrait', 'landscape') + '"')
			IF xl_sheets.paperwidth > 0 .AND. xl_sheets.paperheight > 0
				FWRITE(lhShFile, ' paperHeight="' + TRANSFORM(xl_sheets.paperheight) + xl_sheets.paperdimen + '"')
				FWRITE(lhShFile, ' paperWidth="' + TRANSFORM(xl_sheets.paperwidth) + xl_sheets.paperdimen + '"')
			ELSE
				IF xl_sheets.papersize > 0
					FWRITE(lhShFile, ' paperSize="' + TRANSFORM(xl_sheets.papersize) + '"')
				ENDIF
			ENDIF
			DO CASE
				CASE xl_sheets.fittowidth > 0 .AND. xl_sheets.fittoheight > 0
					FWRITE(lhShFile, ' fitToWidth="'  + TRANSFORM(xl_sheets.fittowidth) + '"')
					FWRITE(lhShFile, ' fitToHeight="' + TRANSFORM(xl_sheets.fittoheight) + '"')
		
				CASE xl_sheets.fittowidth = 0 .AND. xl_sheets.fittoheight > 0
					FWRITE(lhShFile, ' fitToHeight="' + TRANSFORM(xl_sheets.fittoheight) + '"')
		
				CASE xl_sheets.fittowidth > 0 .AND. xl_sheets.fittoheight = 0
					FWRITE(lhShFile, ' fitToWidth="'  + TRANSFORM(xl_sheets.fittowidth) + '"')
				
				OTHERWISE
					IF xl_sheets.scale != 100
						FWRITE(lhShFile, ' scale="' + TRANSFORM(xl_sheets.scale) + '"')
					ENDIF
			ENDCASE
			FWRITE(lhShFile, '/>')
		
		*-*	Write the header and footer for the sheet if defined
			IF SEEK(BINTOC(tnWB)+BINTOC(tnSheet), "xl_hdrfooterdefn", "wbsheet")
				FWRITE(lhShFile, '<headerFooter')
				IF xl_hdrfooterdefn.alignmargin
					FWRITE(lhShFile, ' alignWithMargins="1"')
				ENDIF
				IF xl_hdrfooterdefn.difffirstpg
					FWRITE(lhShFile, ' differentFirst="1"')
				ENDIF
				IF xl_hdrfooterdefn.diffoddeven
					FWRITE(lhShFile, ' differentOddEven="1"')
				ENDIF
				IF xl_hdrfooterdefn.scalewdoc
					FWRITE(lhShFile, ' scaleWithDoc="1"')
				ENDIF
				FWRITE(lhShFile, '>')
		
		*-*		Get the page header text
				lcOddText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_HDR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddHeader>', lcOddText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_HDR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddHeader>', lcOddText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_HDR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddHeader>', lcOddText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcOddText)
					lcOddText = lcOddText + '</oddHeader>'
				ENDIF
		
				lcEvenText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_HDR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenHeader>', lcEvenText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_HDR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenHeader>', lcEvenText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_HDR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenHeader>', lcEvenText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcEvenText)
					lcEvenText = lcEvenText + '</evenHeader>'
				ENDIF
		
				lcSameText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_HDR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddHeader>', lcSameText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_HDR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddHeader>', lcSameText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_HDR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddHeader>', lcSameText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcSameText)
					lcSameText = lcSameText + '</oddHeader>'
				ENDIF
				IF EMPTY(lcSameText)
					lcHeaderText = lcOddText + lcEvenText
				ELSE
					lcHeaderText = lcSameText
				ENDIF
		
		*-*		Get the page footer text
				lcOddText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_FTR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddFooter>', lcOddText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_FTR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddFooter>', lcOddText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_ODD_PAGE, HEADERFOOTER_SECT_FTR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcOddText = IIF(EMPTY(lcOddText), '<oddFooter>', lcOddText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcOddText)
					lcOddText = lcOddText + '</oddFooter>'
				ENDIF
		
				lcEvenText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_FTR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenFooter>', lcEvenText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_FTR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenFooter>', lcEvenText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_EVEN_PAGE, HEADERFOOTER_SECT_FTR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcEvenText = IIF(EMPTY(lcEvenText), '<evenFooter>', lcEvenText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcEvenText)
					lcEvenText = lcEvenText + '</evenFooter>'
				ENDIF
		
				lcSameText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_FTR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddFooter>', lcSameText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_FTR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddFooter>', lcSameText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_SAME_PAGE, HEADERFOOTER_SECT_FTR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcSameText = IIF(EMPTY(lcSameText), '<oddFooter>', lcSameText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcSameText)
					lcSameText = lcSameText + '</oddFooter>'
				ENDIF
				IF EMPTY(lcSameText)
					lcFooterText = lcOddText + lcEvenText
				ELSE
					lcFooterText = lcSameText
				ENDIF
		
		*-*		Get the first page text
				lcFirstHdrText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_HDR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcFirstHdrText = IIF(EMPTY(lcFirstHdrText), '<firstHeader>', lcFirstHdrText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_HDR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcFirstHdrText = IIF(EMPTY(lcFirstHdrText), '<firstHeader>', lcFirstHdrText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_HDR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcFirstHdrText = IIF(EMPTY(lcFirstHdrText), '<firstHeader>', lcFirstHdrText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcFirstHdrText)
					lcFirstHdrText = lcFirstHdrText + '</firstHeader>'
				ENDIF
		
				lcFirstFtrText = ""
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_FTR_LEFT), "xl_hdrfootertext", "hdrfootr")
					lcFirstFtrText = IIF(EMPTY(lcFirstFtrText), '<firstFooter>', lcFirstFtrText) + '&amp;L&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_FTR_CENTER), "xl_hdrfootertext", "hdrfootr")
					lcFirstFtrText = IIF(EMPTY(lcFirstFtrText), '<firstFooter>', lcFirstFtrText) + '&amp;C&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF SEEK(this.GetHeaderFooterKeyExpr(tnWB, tnSheet, HEADERFOOTER_FIRST_PAGE, HEADERFOOTER_SECT_FTR_RIGHT), "xl_hdrfootertext", "hdrfootr")
					lcFirstFtrText = IIF(EMPTY(lcFirstFtrText), '<firstFooter>', lcFirstFtrText) + '&amp;R&amp;' + this.GetXMLHeaderFooterText()
				ENDIF
				IF !EMPTY(lcFirstFtrText)
					lcFirstFtrText = lcFirstFtrText + '</firstFooter>'
				ENDIF
		*-*		Write the header/footer text to XML
				FWRITE(lhShFile, lcHeaderText + lcFooterText + lcFirstHdrText + lcFirstFtrText + '</headerFooter>')
			ENDIF
			
		*-*	Write the drawing section
			SELECT relid FROM xl_relationships WHERE workbook = tnWB AND sheet = tnSheet AND reltype = RELS_TYPE_DRAWING INTO ARRAY laRelId
			IF VARTYPE(laRelId[1]) = "N"
				FWRITE(lhShFile, '<drawing r:id="rId' + TRANSFORM(laRelId[1]) + '"/>')
			ENDIF
		
			FWRITE(lhShFile, '</worksheet>')
			llSuccess = True
		
		*-*	Write the sheet relationship file
			IF !this.WriteSheetRelationshipsXML(tnWB, tnSheet, tcPath)
				THROW
			ENDIF
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
			this.ErrorLevelId = 14
			RAISEEVENT(this, "OnShowErrorMessage", 14, loException.message)
		
		FINALLY
			FCLOSE(lhShFile)
			IF ISNULL(loException)
				this.SaveAsUTF8(lcFileName)
			ENDIF
		ENDTRY
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteStringsXML		&& Writes the sharedStrings XML
		LPARAMETERS tnWB, tcPath, tnTotStrCnt, tnUnqStrCnt
		LOCAL lcFileName, lhFile, loException, llSuccess, lnRelId
		RAISEEVENT(this, "OnShowStatusMessage", 2, 2)
		TRY
		*-*	Output the file		
			lcFileName = tcPath + "xl\sharedStrings.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
		
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + LF)
			FWRITE(lhFile, '<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="' + TRANSFORM(tnTotStrCnt) + '" uniqueCount="' + TRANSFORM(tnUnqStrCnt) + '">')   && Change 19Jun2018: missplaced >
		
			SELECT xl_strings
			SCAN FOR workbook = tnWB .AND. !DELETED()
				IF xl_strings.formatted
					FWRITE(lhFile, '<si>')
					SELECT xl_strformat
					SCAN FOR workbook = tnWB .AND. id = xl_strings.id .AND. !DELETED()
						FWRITE(lhFile, '<r>')
						IF xl_strformat.fbold .OR. xl_strformat.fitalic .OR. xl_strformat.fcolor>0 .OR. !EMPTY(xl_strformat.fname) .OR. xl_strformat.fsize>0 ;
								.OR. xl_strformat.strkthr .OR. !EMPTY(xl_strformat.fvpos) .OR. xl_strformat.theme>0 .OR. xl_strformat.indexed>0 .OR. xl_strformat.uline != UNDERLINE_NONE
		
							FWRITE(lhFile, '<rPr>')
							IF xl_strformat.fbold
								FWRITE(lhFile, '<b/>')
							ENDIF
							IF xl_strformat.fitalic
								FWRITE(lhFile, '<i/>')
							ENDIF
							IF xl_strformat.uline != UNDERLINE_NONE
								FWRITE(lhFile, '<u/>')
							ENDIF
							IF xl_strformat.strkthr
								FWRITE(lhFile, '<strike/>')
							ENDIF
							IF xl_strformat.fsize > 0
								FWRITE(lhFile, '<sz val="' + TRANSFORM(xl_strformat.fsize) + '"/>')
							ENDIF
							DO CASE
								CASE xl_strformat.fcolor > 0
									FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_strformat.fcolor) +'"/>')
		
								CASE xl_strformat.indexed > 0
									FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_strformat.indexed) + '"/>')
		
								CASE xl_strformat.theme > 0
									FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_strformat.theme) +'" tint="' + TRANSFORM(xl_strformat.tint, "99.999999999999999") + '"/>')
							ENDCASE
							IF !EMPTY(xl_strformat.fvpos)
								FWRITE(lhFile, '<vertAlign val="' + ALLTRIM(xl_strformat.fvpos) + '"/>')
							ENDIF
							IF !EMPTY(xl_strformat.fname)
								FWRITE(lhFile, '<rFont val="' + ALLTRIM(xl_strformat.fname) + '"/>')
							ENDIF
							FWRITE(lhFile, '</rPr>')
						ENDIF
		*				IF xl_strformat.presvspace
							FWRITE(lhFile, '<t xml:space="preserve">' + CHRTRAN(xl_strformat.stringxml, CHR(160), CHR(32)) + '</t>')
		*				ELSE
		*					FWRITE(lhFile, '<t>' + xl_strformat.stringxml + '</t>')
		*				ENDIF
						FWRITE(lhFile, '</r>')
					ENDSCAN
					FWRITE(lhFile, '</si>')
					SELECT xl_strings
				ELSE
					IF xl_strings.presvspace
						FWRITE(lhFile, '<si><t xml:space="preserve">' + CHRTRAN(xl_strings.stringxml, CHR(160), CHR(32)) + '</t></si>')
					ELSE
						FWRITE(lhFile, '<si><t>' + xl_strings.stringxml + '</t></si>')
					ENDIF
				ENDIF
			ENDSCAN
			SET ORDER TO
			FWRITE(lhFile, '</sst>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
			llSuccess = True
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
			this.ErrorLevelId = 15
			RAISEEVENT(this, "OnShowErrorMessage", 15, loException.message)
		ENDTRY
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteStylesXML		&& Writes the styles.xml support file
		LPARAMETERS tnWB, tcPath
		LOCAL lcAlias, lcFileName, lhFile, lnNumFmtsId, lnRecCount, lcNumFmtId, lnRotation, llIndexedColors
		LOCAL llMruColors
		RAISEEVENT(this, "OnShowStatusMessage", 2, 3)
		TRY
			lcAlias = ALIAS()
			lcFileName = tcPath + "xl\styles.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ')
			FWRITE(lhFile, 'xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" ')
			FWRITE(lhFile, 'mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">')
		
		*-*	Get last used custom number format Id
			lnNumFmtsId = 0
			SELECT xl_numfmts
			SET ORDER TO TAG sortorder
			SCAN FOR workbook = tnWB .AND. xl_numfmts.id < 900
				IF xl_numfmts.id > lnNumFmtsId
					lnNumFmtsId = xl_numfmts.id
				ENDIF
			ENDSCAN
		
		*-*	Write the Number format section
			COUNT FOR workbook = tnWB TO lnRecCount
			FWRITE(lhFile, '<numFmts count="' + TRANSFORM(lnRecCount) + '">')
			SCAN FOR workbook = tnWB
				IF xl_numfmts.id > 900
					lnNumFmtsId = lnNumFmtsId + 1
					FWRITE(lhFile, '<numFmt numFmtId="' + TRANSFORM(lnNumFmtsId) + '" formatCode="' + ALLTRIM(xl_numfmts.formatxml) + '"/>')
					REPLACE xl_numfmts.tempid WITH lnNumFmtsId IN xl_numfmts
				ELSE
					FWRITE(lhFile, '<numFmt numFmtId="' + TRANSFORM(xl_numfmts.id) + '" formatCode="' + ALLTRIM(xl_numfmts.formatxml) + '"/>')
					REPLACE xl_numfmts.tempid WITH xl_numfmts.id IN xl_numfmts
				ENDIF
			ENDSCAN
			FWRITE(lhFile, '</numFmts>')
			SET ORDER TO
		
		*-*	Write the fonts section
			SELECT xl_fonts
			SET ORDER TO TAG sortorder
			COUNT FOR workbook = tnWB TO lnRecCount
			FWRITE(lhFile, '<fonts count="' + TRANSFORM(lnRecCount) + '" x14ac:knownFonts="1">')
			SCAN FOR workbook = tnWB
				FWRITE(lhFile, '<font>')
				FWRITE(lhFile, '<name val="' + ALLTRIM(xl_fonts.fname) + '"/>')
				FWRITE(lhFile, '<sz val="' + TRANSFORM(xl_fonts.fsize) + '"/>')
				IF xl_fonts.fbold
					FWRITE(lhFile, '<b/>')
				ENDIF
				IF xl_fonts.fitalic
					FWRITE(lhFile, '<i/>')
				ENDIF
				IF !EMPTY(xl_fonts.uline)
					FWRITE(lhFile, '<u val="' + ALLTRIM(xl_fonts.uline) + '"/>')
				ENDIF
				IF xl_fonts.strkthr
					FWRITE(lhFile, '<strike/>')
				ENDIF
				IF !EMPTY(xl_fonts.fvpos)
					FWRITE(lhFile, '<vertAlign val="' + ALLTRIM(xl_fonts.fvpos) + '"/>')
				ENDIF
				DO CASE
					CASE xl_fonts.fcolor > 0
						FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_fonts.fcolor) + '"/>')
		
					CASE xl_fonts.indexed > 0
						FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_fonts.indexed) + '"/>')
					
					CASE xl_fonts.theme > 0
						FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_fonts.theme) + '" tint="' + TRANSFORM(xl_fonts.tint, "99.999999999999999") + '"/>')
				ENDCASE
				FWRITE(lhFile, '</font>')
			ENDSCAN
			FWRITE(lhFile, '</fonts>')
			SET ORDER TO
		
		*-*	Write the fills section
			SELECT xl_fills
			SET ORDER TO TAG sortorder
			COUNT FOR workbook = tnWB TO lnRecCount
			FWRITE(lhFile, '<fills count="' + TRANSFORM(lnRecCount) + '">')
			SCAN FOR workbook = tnWB
				FWRITE(lhFile, '<fill><patternFill patternType="' + ALLTRIM(xl_fills.patttype) + '"')
				DO CASE
					CASE xl_fills.fgindexed > 0 .AND. xl_fills.bgindexed > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor indexed="' + TRANSFORM(xl_fills.fgindexed) + '"/>')
						FWRITE(lhFile, '<bgColor indexed="' + TRANSFORM(xl_fills.bgindexed) + '"/>')
						FWRITE(lhFile, '</patternFill>')
		
					CASE xl_fills.fgcolor > 0 .AND. xl_fills.bgindexed > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor rgb="' + this.ConvertColorToHex(xl_fills.fgcolor) + '"/>')
						FWRITE(lhFile, '<bgColor indexed="' + TRANSFORM(xl_fills.bgindexed) + '"/>')
						FWRITE(lhFile, '</patternFill>')
					
					CASE xl_fills.bgcolor > 0 .AND. xl_fills.fgindexed > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor indexed="' + TRANSFORM(xl_fills.fgindexed) + '"/>')
						FWRITE(lhFile, '<bgColor rgb="' + this.ConvertColorToHex(xl_fills.bgcolor) + '"/>')
						FWRITE(lhFile, '</patternFill>')
		
					CASE xl_fills.fgcolor > 0 .AND. xl_fills.bgcolor > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor rgb="' + this.ConvertColorToHex(xl_fills.fgcolor) + '"/>')
						FWRITE(lhFile, '<bgColor rgb="' + this.ConvertColorToHex(xl_fills.bgcolor) + '"/>')
						FWRITE(lhFile, '</patternFill>')
		
					CASE xl_fills.fgtheme > -1 .AND. xl_fills.bgindexed > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor theme="' + TRANSFORM(xl_fills.fgtheme) + '"/>')
						FWRITE(lhFile, '<bgColor indexed="' + TRANSFORM(xl_fills.bgindexed) + '"/>')
						FWRITE(lhFile, '</patternFill>')
		
					CASE xl_fills.bgtheme > -1 .AND. xl_fills.bgindexed > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<bgColor theme="' + TRANSFORM(xl_fills.bgtheme) + '"/>')
						FWRITE(lhFile, '<fgColor indexed="' + TRANSFORM(xl_fills.fgindexed) + '"/>')
						FWRITE(lhFile, '</patternFill>')
		
					CASE xl_fills.theme > 0
						FWRITE(lhFile, '>')
						FWRITE(lhFile, '<fgColor theme="' + TRANSFORM(xl_fills.theme) + '" tint="' + TRANSFORM(xl_fills.tint, "99.999999999999999") + '"/>')
						FWRITE(lhFile, '<bgColor indexed="64"/>')
						FWRITE(lhFile, '</patternFill>')
					
					OTHERWISE
						FWRITE(lhFile, '/>')
				ENDCASE
				FWRITE(lhFile, '</fill>')
			ENDSCAN
			FWRITE(lhFile, '</fills>')
		
		*-*	Write the borders section
			SELECT xl_borders
			SET ORDER TO TAG sortorder
			COUNT FOR workbook = tnWB TO lnRecCount
			FWRITE(lhFile, '<borders count="' + TRANSFORM(lnRecCount) + '">')
			SCAN FOR workbook = tnWB
				FWRITE(lhFile, '<border' + IIF(xl_borders.diagdn=1, ' diagonalDown="1"', '') + IIF(xl_borders.diagup=1, ' diagonalUp="1"', '') +'>')
				IF EMPTY(xl_borders.lstyle)
					FWRITE(lhFile, '<left/>')
				ELSE
					IF xl_borders.lstyle = BORDER_STYLE_NONE
						FWRITE(lhFile, '<left')
					ELSE
						FWRITE(lhFile, '<left style="' + ALLTRIM(xl_borders.lstyle) + '"')
					ENDIF
					DO CASE
						CASE xl_borders.lcolor > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_borders.lcolor) + '"/>')
							FWRITE(lhFile, '</left>')
		
						CASE xl_borders.lindexed > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_borders.lindexed) + '"/>')
							FWRITE(lhFile, '</left>')
		
						CASE xl_borders.ltheme > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_borders.ltheme) +'" tint="' + TRANSFORM(xl_borders.ltint, "99.999999999999999") + '"/>')
							FWRITE(lhFile, '</left>')
		
						OTHERWISE
							FWRITE(lhFile, '/>')
					ENDCASE
				ENDIF
				IF EMPTY(xl_borders.rstyle)
					FWRITE(lhFile, '<right/>')
				ELSE
					IF xl_borders.rstyle = BORDER_STYLE_NONE
						FWRITE(lhFile, '<right')
					ELSE
						FWRITE(lhFile, '<right style="' + ALLTRIM(xl_borders.rstyle) + '"')
					ENDIF
					DO CASE
						CASE xl_borders.rcolor > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_borders.rcolor) + '"/>')
							FWRITE(lhFile, '</right>')
		
						CASE xl_borders.rindexed > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_borders.rindexed) + '"/>')
							FWRITE(lhFile, '</right>')
		
		
						CASE xl_borders.rtheme > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_borders.rtheme) +'" tint="' + TRANSFORM(xl_borders.rtint, "99.999999999999999") + '"/>')
							FWRITE(lhFile, '</right>')
		
						OTHERWISE
							FWRITE(lhFile, '/>')
					ENDCASE
				ENDIF
				IF EMPTY(xl_borders.tstyle)
					FWRITE(lhFile, '<top/>')
				ELSE
					IF xl_borders.tstyle = BORDER_STYLE_NONE
						FWRITE(lhFile, '<top')
					ELSE
						FWRITE(lhFile, '<top style="' + ALLTRIM(xl_borders.tstyle) + '"')
					ENDIF
					DO CASE
						CASE xl_borders.tcolor > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_borders.tcolor) + '"/>')
							FWRITE(lhFile, '</top>')
		
						CASE xl_borders.tindexed > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_borders.tindexed) + '"/>')
							FWRITE(lhFile, '</top>')
		
						CASE xl_borders.ttheme > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_borders.ttheme) +'" tint="' + TRANSFORM(xl_borders.ttint, "99.999999999999999") + '"/>')
							FWRITE(lhFile, '</top>')
		
						OTHERWISE
							FWRITE(lhFile, '/>')
					ENDCASE
				ENDIF
				IF EMPTY(xl_borders.bstyle)
					FWRITE(lhFile, '<bottom/>')
				ELSE
					IF xl_borders.bstyle = BORDER_STYLE_NONE
						FWRITE(lhFile, '<bottom')
					ELSE
						FWRITE(lhFile, '<bottom style="' + ALLTRIM(xl_borders.bstyle) + '"')
					ENDIF
					DO CASE
						CASE xl_borders.bcolor > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_borders.bcolor) + '"/>')
							FWRITE(lhFile, '</bottom>')
		
						CASE xl_borders.bindexed > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_borders.bindexed) + '"/>')
							FWRITE(lhFile, '</bottom>')
		
						CASE xl_borders.btheme > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_borders.btheme) +'" tint="' + TRANSFORM(xl_borders.btint, "99.999999999999999") + '"/>')
							FWRITE(lhFile, '</bottom>')
		
						OTHERWISE
							FWRITE(lhFile, '/>')
					ENDCASE
				ENDIF
				IF EMPTY(xl_borders.dstyle)
					FWRITE(lhFile, '<diagonal/>')
				ELSE
					IF xl_borders.dstyle = BORDER_STYLE_NONE
						FWRITE(lhFile, '<diagonal')
					ELSE
						FWRITE(lhFile, '<diagonal style="' + ALLTRIM(xl_borders.dstyle) + '"')
					ENDIF
					DO CASE
						CASE xl_borders.dcolor > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color rgb="' + this.ConvertColorToHex(xl_borders.dcolor) + '"/>')
							FWRITE(lhFile, '</diagonal>')
		
						CASE xl_borders.dindexed > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color indexed="' + TRANSFORM(xl_borders.dindexed) + '"/>')
							FWRITE(lhFile, '</diagonal>')
		
						CASE xl_borders.dtheme > 0
							FWRITE(lhFile, '>')
							FWRITE(lhFile, '<color theme="' + TRANSFORM(xl_borders.dtheme) +'" tint="' + TRANSFORM(xl_borders.dtint, "99.999999999999999") + '"/>')
							FWRITE(lhFile, '</diagonal>')
		
						OTHERWISE
							FWRITE(lhFile, '/>')
					ENDCASE
				ENDIF
				FWRITE(lhFile, '</border>')
			ENDSCAN
			FWRITE(lhFile, '</borders>')
		*-*	Write the cellStyleXfs section
		
		*-*	Write the cellXfs section
			SELECT xl_cellxfs
			SET ORDER TO TAG sortorder
			COUNT FOR workbook = tnWB TO lnRecCount
			FWRITE(lhFile, '<cellXfs count="' + TRANSFORM(lnRecCount) + '">')
			SCAN FOR workbook = tnWB
				IF xl_cellxfs.numFmtId > 0 .AND. SEEK(BINTOC(tnWB)+BINTOC(xl_cellxfs.numFmtId), "xl_numfmts", "id")
					lcNumFmtId = TRANSFORM(xl_numfmts.tempid)
				ELSE
					lcNumFmtId = TRANSFORM(xl_cellxfs.numFmtId)
				ENDIF
				FWRITE(lhFile, '<xf numFmtId="' + lcNumFmtId + '" fontId="' + TRANSFORM(xl_cellxfs.fontid) + '"')
				FWRITE(lhFile,    ' fillId="' + TRANSFORM(xl_cellxfs.fillid) + '"')
				FWRITE(lhFile,    ' borderId="' + TRANSFORM(xl_cellxfs.borderid) + '"')
				FWRITE(lhFile,    IIF(lcNumFmtId = '0', '', ' applyNumberFormat="1"'))
				FWRITE(lhFile,    IIF(xl_cellxfs.applyfont   = 1, ' applyFont="1"', ''))
				FWRITE(lhFile,    IIF(xl_cellxfs.fillid      = 1, ' applyFill="1"', ''))
				FWRITE(lhFile,    IIF(xl_cellxfs.applyborder = 1, ' applyBorder="1"', ''))
				FWRITE(lhFile,    IIF(xl_cellxfs.applyalign  = 1, ' applyAlignment="1"', ''))
				FWRITE(lhFile,    IIF(xl_cellxfs.applyprot   = 1, ' applyProtection="1"', ''))
				FWRITE(lhFile, '>')
		
				IF !EMPTY(xl_cellxfs.halign) .OR. !EMPTY(xl_cellxfs.valign) .OR. xl_cellxfs.wraptext > 0 .OR. xl_cellxfs.indent > 0 .OR. xl_cellxfs.rotation > 0
					FWRITE(lhFile, '<alignment')
					IF !EMPTY(xl_cellxfs.halign)
						FWRITE(lhFile, ' horizontal="' + ALLTRIM(xl_cellxfs.halign) + '"')
					ENDIF
					IF !EMPTY(xl_cellxfs.valign)
						FWRITE(lhFile, ' vertical="' + ALLTRIM(xl_cellxfs.valign) + '"')
					ENDIF
					IF xl_cellxfs.wraptext > 0
						FWRITE(lhFile, ' wrapText="1"')
					ENDIF
					IF xl_cellxfs.indent > 0
						FWRITE(lhFile, ' indent="' + TRANSFORM(xl_cellxfs.indent) + '"')
					ENDIF
					IF xl_cellxfs.rotation != 0
						FWRITE(lhFile, ' textRotation="' + TRANSFORM(xl_cellxfs.rotation) + '"')
					ENDIF
					FWRITE(lhFile, '/>')
				ENDIF
				IF xl_cellxfs.celllocked = 0 .OR. xl_cellxfs.cellhidden = 1
					FWRITE(lhFile, '<protection')
					FWRITE(lhFile, ' locked="' + TRANSFORM(xl_cellxfs.celllocked) + '"')
					FWRITE(lhFile, ' hidden="' + TRANSFORM(xl_cellxfs.cellhidden) + '"')
					FWRITE(lhFile, '/>')
				ENDIF
				FWRITE(lhFile, '</xf>')
			ENDSCAN
			FWRITE(lhFile, '</cellXfs>')
		
		*-*	Write the colors section
			llIndexedColors = SEEK(BINTOC(tnWB)+BINTOC(1), "xl_ndxcolors", "indexid")
			llMruColors     = SEEK(BINTOC(tnWB)+BINTOC(1), "xl_mrucolors", "indexid")
			IF llIndexedColors .OR. llMruColors
				FWRITE(lhFile, '<colors>')
				IF llIndexedColors
					FWRITE(lhFile, '<indexedColors>')
					SELECT xl_ndxcolors
					SET ORDER TO TAG indexid
					SCAN FOR workbook = tnWB
						FWRITE(lhFile, '<rgbColor rgb="' + xl_ndxcolors.rgbcolor + '"/>')
					ENDSCAN
					FWRITE(lhFile, '</indexedColors>')
				ENDIF
		
				IF llMruColors
					FWRITE(lhFile, '<mruColors>')
					SELECT xl_mrucolors
					SET ORDER TO TAG indexid
					SCAN FOR workbook = tnWB
						FWRITE(lhFile, '<color rgb="' + xl_ndxcolors.rgbcolor + '"/>')
					ENDSCAN
					FWRITE(lhFile, '</mruColors>')
				ENDIF
				FWRITE(lhFile, '</colors>')
			ENDIF
		
		*-*	Close the style sheet
			FWRITE(lhFile, '</styleSheet>')
			llSuccess = True
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
			this.ErrorLevelId = 17
			RAISEEVENT(this, "OnShowErrorMessage", 17, "Unable to create styles.xml")
		
		FINALLY
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		ENDTRY
		IF !EMPTY(lcAlias)
			SELECT &lcAlias
		ENDIF
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteSupportXMLs		&& Writes the [Content_Types].xml, .rels, core.xml, workbook.xml.rels and app.xml files
		LPARAMETERS tnWB, tcPath
		LOCAL llSuccess, loException, lhFile, lnSheet, lnUniqueCnt, lcCurr, lcColor, lnTotCount, lcFileName, lcBaseContentType
		RAISEEVENT(this, "OnShowStatusMessage", 2, 1)
		TRY
		
		*-*	Save the .rels XML
			lcFileName = tcPath + "_rels\.rels"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">')
			FWRITE(lhFile, '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>')
			FWRITE(lhFile, '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>')
			FWRITE(lhFile, '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>')
			FWRITE(lhFile, '</Relationships>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		*-*	Save the app XML
			lcFileName = tcPath + "docProps\app.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">')
			FWRITE(lhFile, '<Application>ExcelXMLX Release ' + TRANSFORM(this.ExcelXlsxRelease) + '</Application>')
			IF !EMPTY(this.CompanyName)
				FWRITE(lhFile, '<Company>' + this.CompanyName + '</Company>')
			ENDIF
			FWRITE(lhFile, '</Properties>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
		*-*	Save the core XML
			lcFileName = tcPath + "docProps\core.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>')
			FWRITE(lhFile, '<cp:coreProperties ')
			FWRITE(lhFile, 'xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ')
			FWRITE(lhFile, 'xmlns:dc="http://purl.org/dc/elements/1.1/" ')
			FWRITE(lhFile, 'xmlns:dcterms="http://purl.org/dc/terms/" ')
			FWRITE(lhFile, 'xmlns:dcmitype="http://purl.org/dc/dcmitype/" ')
			FWRITE(lhFile, 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">')
			IF !EMPTY(this.Title)
			    FWRITE(lhFile, '<dc:title>' + this.GetXMLString(this.Title) + '</dc:title>')
			ENDIF
			IF !EMPTY(this.Subject)
			    FWRITE(lhFile, '<dc:subject>' + this.GetXMLString(this.Subject) + '</dc:subject>')
			ENDIF
			IF !EMPTY(this.CreatorName)
				FWRITE(lhFile, '<dc:creator>' + this.GetXMLString(this.CreatorName) + '</dc:creator>')
			ENDIF
			IF !EMPTY(this.UserName)
				FWRITE(lhFile, '<cp:lastModifiedBy>' + this.GetXMLString(this.UserName) + '</cp:lastModifiedBy>')
			ENDIF
			IF VERSION(5) < 900
				FWRITE(lhFile, '<dcterms:created xsi:type="dcterms:W3CDTF">' + this.TTOC3(DATETIME()) + 'Z</dcterms:created>')
				FWRITE(lhFile, '<dcterms:modified xsi:type="dcterms:W3CDTF">' + this.TTOC3(DATETIME()) + 'Z</dcterms:modified>')
			ELSE
				FWRITE(lhFile, '<dcterms:created xsi:type="dcterms:W3CDTF">' + TTOC(DATETIME(), 3) + 'Z</dcterms:created>')
				FWRITE(lhFile, '<dcterms:modified xsi:type="dcterms:W3CDTF">' + TTOC(DATETIME(), 3) + 'Z</dcterms:modified>')
			ENDIF
			FWRITE(lhFile, '</cp:coreProperties>')
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		
			llSuccess = True
		
		CATCH TO loException
			SET STEP ON
			llSuccess = False
			this.ErrorLevelId = 18
			RAISEEVENT(this, "OnShowErrorMessage", 18, "Unable to create workbook supporting XMLs")
		ENDTRY
		RETURN llSuccess
	ENDPROC

	PROTECTED PROCEDURE WriteWorkbookXML		&& Writes the workbook XML
		LPARAMETERS tnWB, tcPath
		LOCAL lcFileName, lhFile, loException, llSuccess, lcSheetName
		RAISEEVENT(this, "OnShowStatusMessage", 2, 4)
		TRY
			lcFileName = tcPath + "xl\workbook.xml"
			lhFile = FCREATE(lcFileName)
			IF lhFile < 0
				THROW
			ENDIF
			FWRITE(lhFile, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ LF)
			FWRITE(lhFile, '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">')
		
		*-*	Write the workbook protection
			IF xl_workbooks.protection
				FWRITE(lhFile, '<workbookProtection workbookPassword="' + ALLTRIM(xl_workbooks.password) + '"')
				IF !EMPTY(xl_workbooks.algorithm)
					FWRITE(lhFile, ' workbookAlgorithmName="' + ALLTRIM(xl_workbooks.algorithm) + '"')
				ENDIF
				FWRITE(lhFile, ' lockStructure="1"/>')
			ENDIF
		
		*-*	Write thw sheet information
			FWRITE(lhFile, '<sheets>')
			SELECT xl_relationships
			SET ORDER TO TAG relid
			SCAN FOR workbook = tnWB .AND. reltype = RELS_TYPE_WORKSHEET .AND. relsrc = RELS_SRC_WORKBOOK
				IF SEEK(BINTOC(tnWB)+BINTOC(xl_relationships.sheet), "xl_sheets", "sheet")
					FWRITE(lhFile, '<sheet r:id="rId' + TRANSFORM(xl_relationships.relid) + '" sheetId="' + TRANSFORM(xl_sheets.sheet) + '"')
					DO CASE
						CASE xl_sheets.state = VISIBLE_SHEET_STATE
			
						CASE xl_sheets.state = HIDDEN_SHEET_STATE
							FWRITE(lhFile, ' state="hidden"')
						
						CASE xl_sheets.state = VERYHIDDEN_SHEET_STATE
							FWRITE(lhFile, ' state="veryHidden"')
					ENDCASE
					FWRITE(lhFile, ' name="' + this.GetXMLString(ALLTRIM(xl_sheets.shname)) + '"/>')
				ELSE
					THROW
				ENDIF
			ENDSCAN
			SET ORDER TO
			FWRITE(lhFile, '</sheets>')
		
		*-*	Write the external references information
			IF SEEK(BINTOC(tnWB)+BINTOC(0)+RELS_SRC_WORKBOOK+RELS_TYPE_EXTNLINK, "xl_relationships", "relsrctype")
				FWRITE(lhFile, '<externalReferences>')
				SCAN FOR workbook = tnWB AND sheet = 0 AND relsrc = RELS_SRC_WORKBOOK AND reltype = RELS_TYPE_EXTNLINK
					FWRITE(lhFile, '<externalReference r:id="rId' + TRANSFORM(xl_relationships.relid) + '"/>')
				ENDSCAN
				FWRITE(lhFile, '</externalReferences>')
			ENDIF
		
		*-*	Write the named range information
			SELECT xl_namerange
			LOCATE FOR workbook = tnWB .AND. !DELETED()
			IF FOUND()
				FWRITE(lhFile, '<definedNames>')
				SCAN FOR workbook = tnWB .AND. !DELETED()
					IF (xl_namerange.begcol = 0 .AND. xl_namerange.begrow = 0) .OR. (xl_namerange.begcol = 0 .AND. xl_namerange.begrow = 0)
						LOOP
					ENDIF
					FWRITE(lhFile, '<definedName name="' + ALLTRIM(xl_namerange.rname) + '"')
					IF !EMPTY(xl_namerange.comment)
						FWRITE(lhFile, ' comment="' + ALLTRIM(xl_namerange.comment) + '"')
					ENDIF
					IF xl_namerange.scope = SCOPE_SH_NAMED_RANGE
						FWRITE(lhFile, ' localSheetId="' + TRANSFORM(xl_namerange.sheet - 1) + '"')
					ENDIF
					FWRITE(lhFile, '>')
		
					lcSheetName = this.GetSheetName(tnWB, xl_namerange.sheet)
					FWRITE(lhFile, IIF(OCCURS(" ", lcSheetName)>0, "'", "") + lcSheetName + IIF(OCCURS(" ", lcSheetName)>0, "'", ""))
					DO CASE
						CASE xl_namerange.begcol = 0 .AND. xl_namerange.begrow > 0
							FWRITE(lhFile, '!$' + TRANSFORM(xl_namerange.begrow))
		
						CASE xl_namerange.begcol > 0 .AND. xl_namerange.begrow = 0
							FWRITE(lhFile, '!$' + this.ColumnIndexToAscii(xl_namerange.begcol))
						
						OTHERWISE
							FWRITE(lhFile, '!$' + this.ColumnIndexToAscii(xl_namerange.begcol) + '$' + TRANSFORM(xl_namerange.begrow))
					ENDCASE
					DO CASE
						CASE xl_namerange.endcol = 0 .AND. xl_namerange.endrow > 0
							FWRITE(lhFile, ':$' + TRANSFORM(xl_namerange.endrow))
		
						CASE xl_namerange.endcol > 0 .AND. xl_namerange.begrow = 0
							FWRITE(lhFile, ':$' + this.ColumnIndexToAscii(xl_namerange.endcol))
		
						OTHERWISE
							FWRITE(lhFile, ':$' + this.ColumnIndexToAscii(xl_namerange.endcol) + '$' + TRANSFORM(xl_namerange.endrow))
					ENDCASE
					FWRITE(lhFile, '</definedName>')
				ENDSCAN
				FWRITE(lhFile, '</definedNames>')
			ENDIF
		
		*-*	Close the workbook
			FWRITE(lhFile, '<calcPr calcId="0"/>')
			FWRITE(lhFile, '</workbook>')
		
			llSuccess = True
		
		CATCH TO loException
			FCLOSE(lhFile)
			SET STEP ON
			llSuccess = False
		
		FINALLY
			FCLOSE(lhFile)
			this.SaveAsUTF8(lcFileName)
		ENDTRY
		RETURN llSuccess
	ENDPROC

ENDDEFINE
