spec/layout/xml.cfm
2025-06-02 16:16:51 +03:00

239 lines
9.3 KiB
Plaintext

<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"/>