<cfsilent>
<!---https://www.bennadel.com/blog/461-creating-microsoft-excel-documents-with-coldfusion-and-xml.htm--->
<!---порядок колонок, как в селекте, не управляется никак
Для определения порядка колонок используется поле ATTRIBUTES.titleMap.ordinal, возможно, не самый надежный способ
Можно попробовать использовать порядок колонок в массиве query --->
<cfparam name="ATTRIBUTES.qRead" type="query"/>
<cfparam name="ATTRIBUTES.titleMap" type="struct"/><!---see ../lib/data/field_set.cfm--->
<cfparam name="ATTRIBUTES.sheetTitle" type="string" default="Sheet1"/>
<cfparam name="ATTRIBUTES.filename" type="string" default="export.xlsx"/>
<cfset AVG_SYMBOL_WIDTH=6.5/>
<cfset MAX_COL_LENGTH=30/>

<cfset qRead=ATTRIBUTES.qRead/>

<!---<cfdump var=#qRead#/>
<cfdump var=#ATTRIBUTES.titleMap#/>

<cfabort/>
--->

<!---<cfsavecontent variable="strXmlData">--->

</cfsilent><cfheader
    name="content-disposition"
    value="attachment; filename=#ATTRIBUTES.filename#"
    /><cfcontent
    type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    /><cfoutput><?xml version="1.0"?>
    <?mso-application progid="Excel.Sheet"?>
    <!---
        This is the Workbook root element. This element
        stores characteristics and properties of the
        workbook, such as the namespaces used in
        SpreadsheetML.
    --->
    <Workbook
        xmlns="urn:schemas-microsoft-com:office:spreadsheet"
        xmlns:o="urn:schemas-microsoft-com:office:office"
        xmlns:x="urn:schemas-microsoft-com:office:excel"
        xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
        xmlns:html="http://www.w3.org/TR/REC-html40">
        <!---
            The first child element of the WorkBook element
            is DocumentProperties. Office documents store
            metadata related to the document&#8212;for example,
            the author name, company, creation date, and
            more in the DocumentProperties element.
        --->
        <DocumentProperties
            xmlns="urn:schemas-microsoft-com:office:office">
            <Author>xls-generator</Author>
            <Company></Company>
        </DocumentProperties>
        <!---
            The Styles node represents information related
            to individual styles that can be used to format
            components of the workbook.
        --->
        <Styles>
            <!--- Basic format used by all cells. --->
            <Style ss:ID="Default" ss:Name="Normal">
                <Alignment ss:Vertical="Top"/>
                <Borders/>
                <Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11"/>
                <Interior/>
                <NumberFormat/>
                <Protection/>
            </Style>			
			<Style ss:ID="Th">
				<Alignment ss:Horizontal="Center" ss:Vertical="Top"/>
				<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11" ss:Bold="1"/>
			</Style>
            <Style ss:ID="Int">
                <NumberFormat ss:Format="0" />
            </Style>			
			<Style ss:ID="DateTime">
				<NumberFormat ss:Format="dd/mm/yyyy\ hh:mm;@"/>
			</Style>
<!---
            <Style ss:ID="ShortDate">
                <NumberFormat ss:Format="[ENG][$-409]d\-mmm\-yyyy;@" />
            </Style>

            <Style ss:ID="YesNo">
                <NumberFormat ss:Format="Yes/No" />
            </Style>     
--->
        </Styles>
        <!---
            This defines the first worksheeet and it's name.
            We are only using one worksheet in this example,
            but you could add more Worksheet nodes after
            this one for multiple tabs. The "Name" attribute
            here is the name that shows up in the tab.
        --->
        <Worksheet ss:Name="#ATTRIBUTES.sheetTitle#">
            <Table
                <!---
                    We need a column for each column of the
                    query. This attribute is required to be
                    correct. If the value here does NOT
                    match the data in Excel file, the
                    document will not render properly.
                --->
                ss:ExpandedColumnCount="#ListLen(qRead.ColumnList)#"
                <!---
                    We need a row for every query record
                    plus one for the header row. Again, if
                    this value does not match what is in the
                    document, the excel file will not
                    render properly.
                --->
                ss:ExpandedRowCount="#(qRead.RecordCount + 1)#"
                x:FullColumns="1"
                x:FullRows="1">
                <!---
                    Here, we can define general properties
                    regarding each column in the data output.
					
                <Column ss:Index="1" ss:Width="30" />
                <Column ss:Index="2" ss:Width="100" />
                <Column ss:Index="3" ss:Width="42" />
                <Column ss:Index="4" ss:Width="84" />
                <Column ss:Index="5" ss:Width="66" />
                <Column ss:Index="6" ss:Width="70" />--->
				
				<cfsilent>
					<cfset columns=arrayNew(1)/>
					<cfset i=1/>
					<cfloop struct=#ATTRIBUTES.titleMap# item="field">
						<cfset col=structNew()/>
						<cfset fieldSpec=structFind(ATTRIBUTES.titleMap, field)/>
						<cfset col.name=field/>

						<cfset col.title=fieldSpec.title/>					
						<cfif len(col.title) EQ 0>
							<cfset col.title=col.name/>
						</cfif>
						<cfset col.titleLength=len(col.title)/>					
						
						<cfquery name="qLen" dbtype="query">
							select coalesce(max(length(#field#)),0) as maxlen from qRead
						</cfquery>					
						<cfset col.length=max(col.titleLength, qLen.maxLen)/>
						
						<cfset col.cfSqlType="CF_SQL_VARCHAR"/>			
						<cfif structKeyExists(fieldSpec,"cfSqlType")>
							<cfset col.cfSqlType=#fieldSpec.cfSqlType#/>
						</cfif>
						
						<cfswitch expression=#col.cfSqlType#><!---should use ## around variable name--->
							<cfcase value="CF_SQL_BIGINT,CF_SQL_INTEGER,CF_SQL_SMALLINT,CF_SQL_TINYINT,CF_SQL_BIT,CF_SQL_NUMERIC,CF_SQL_DECIMAL,CF_SQL_REAL,CF_SQL_FLOAT,CF_SQL_DOUBLE">
								<cfset col.ssType="Number"/>
							</cfcase>
							<cfcase value="CF_SQL_TIMESTAMP,CF_SQL_DATE,CF_SQL_TIME,CF_SQL_DATETIME">
								<cfset col.ssType="DateTime"/>
							</cfcase>
							<cfdefaultcase>
								<cfset col.ssType="String"/>
							</cfdefaultcase>
						</cfswitch>
						
						<cfset columns[fieldSpec.ordinal]=col/><!---new col created at each iteration, so we have valid references--->							
						<cfset i=i+1/>
					</cfloop>
				</cfsilent>
				
				<cfloop from=1 to=#arrayLen(columns)# index="i">
					<cfif columns[i].ssType EQ "DateTime"><!--- timestamp string representation is too long, --->
						<Column ss:Index="#i#" ss:Width="#round(min(columns[i].titleLength, MAX_COL_LENGTH)*AVG_SYMBOL_WIDTH)#" ss:AutoFitWidth="1"/>
					<cfelse>
						<Column ss:Index="#i#" ss:Width="#round(min(columns[i].length, MAX_COL_LENGTH)*AVG_SYMBOL_WIDTH)#" ss:AutoFitWidth="1"/>
					</cfif>					
				</cfloop>
                <!---
                    This is our header row. All cells in the
                    header row will be of type string.
                --->				
                <Row>
					<cfloop from=1 to=#arrayLen(columns)# index="i">
                    <Cell ss:StyleID="Th">
                        <Data ss:Type="String">#columns[i].title#</Data>
                    </Cell>
					</cfloop>
                </Row>
					
				<cfflush/>
				
                <cfloop query=#qRead#><!---				
                    ---><Row><!---
						---><cfloop from=1 to=#ArrayLen(columns)# index="i"><!---
							---><cfset field=columns[i].name/><!---					
							---><Cell<cfif columns[i].ssType EQ "DateTime"> ss:StyleID="DateTime"</cfif>><!---
								---><cfif len(qRead[field])><Data ss:Type="#columns[i].ssType#"><!---no extra whitespace allowed here---><!---
									---><cfswitch expression=#columns[i].ssType#><!---
										---><cfcase value="DateTime"><!---
											--->#dateFormat(qRead[field],"YYYY-MM-DD")#T#timeFormat(qRead[field],"HH:mm:ss.l")#<!---
										---></cfcase><!---
										---><cfcase value="Number"><!---
											--->#qRead[field]#<!---
										---></cfcase><!---
										---><cfdefaultcase><!---
											---><![CDATA[#request.clean4CDATA(qRead[field])#]]><!---
										---></cfdefaultcase><!---
									---></cfswitch><!---
								---></Data></cfif><!---
							---></Cell><!---
						---></cfloop><!---
                    ---></Row><!---
                ---></cfloop><!---

            ---></Table>

        </Worksheet>

    </Workbook>

</cfoutput>
<!---</cfsavecontent>--->

<!---
    Define the way in which the browser should interpret
    the content that we are about to stream.
--->
<!---<cfheader
    name="content-disposition"
    value="attachment; filename=#ATTRIBUTES.filename#"
    />--->

<!---
    When streaming the Excel XML data, trim the data and
    replace all the inter-tag white space. No need to stream
    any more content than we have to..ReplaceAll( '>\s+', '>' ).ReplaceAll( '\s+<', '<' ) 
--->
<!---<cfcontent
    type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    variable="#ToBinary( ToBase64( strXmlData.Trim()))#"
    />---><!---<cfabort/>--->
<cfexit method="exittag"/>