<cfcomponent displayname="scaffolder.cfc" hint="I introspect the DBMS and write/update the metadata XML. I read the metadata XML to provide the data to the code generator.">
<!---
Copyright 2006 Objective Internet Ltd - http://www.objectiveinternet.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--->
	
	<cfproperty name="project" type="string" hint="The selected project name." />
	<cfproperty name="template" type="string" hint="The selected template." />
	<cfproperty name="datasource" type="string" hint="The selected datasource name." />
	<cfproperty name="username" type="string" hint="Username to be used to access the datasource." />
	<cfproperty name="password" type="string" hint="Password to be used to access the datasource." />
	<cfproperty name="DbType" type="string" hint="Type of DBMS for the selected datasource." />
	<cfproperty name="DbName" type="string" hint="Database name for the selected datasource." />
	<cfproperty name="DbBuffer" type="string" hint="Size of Driver Buffer." />
	<cfproperty name="aErrorMessages" type="array" hint="An array of error messages." />
	<cfproperty name="xScaffoldingConfig" type="any" hint="An xml document object containing the current configuration." />
	<cfproperty name="configFilePath" type="string" hint="The path to the configuration file." />
	<cfproperty name="selectedTable" type="string" hint="The currently selected table." />
	
	<cffunction name="init" returntype="metadata" output="No" 
				hint="I initialise the metadata object. If the scaffolding file exists I load it." >
		<cfargument name="configFilePath" required="No" type="string" default="#GetDirectoryFromPath(GetBaseTemplatePath())#scaffolding.xml" hint="I am a path to a scaffolding configuration file name." />
		<cfargument name="datasource" required="No" type="string" hint="I am a datasource name." />
		<cfargument name="username" required="No" type="string" default="" hint="I am the username." />
		<cfargument name="password" required="No" type="string" default="" hint="I am the password." />
		<cfargument name="project" required="No" type="string" hint="I am the project name." />
		<cfargument name="template" required="No" type="string" hint="I am the template type." />
		
		<cfset var stDatasources = GetAllDatasources()>
		<cfset var stDBMSLookup = getSupportedDBMS()>
		
		<cfset variables.aErrorMessages = ArrayNew(1)>
		<cfset variables.xScaffoldingConfig = "">
		<cfset variables.configFilePath = arguments.configFilePath>
		<cfset variables.selectedTable = "">
		
		<cfinvoke component="#this#" method="read">
			<cfinvokeargument name="configFilePath" value="#arguments.configFilePath#">
		</cfinvoke>
		
		<cfif (NOT isDefined("variables.datasource")) AND isDefined("arguments.datasource")>
			<cfset variables.datasource=arguments.datasource>
			<cfset variables.username=arguments.username>
			<cfset variables.password=arguments.password>
		</cfif>
		
		<cfif NOT isDefined("variables.datasource")>
			<cfthrow type="Application" message="Datasource not defined."
					 detail="There was no datasource name found in either the #configFilePath# file or the call to the scaffolding init. Please add one and try again.">
		</cfif>
		
		<cfif NOT isDefined("variables.project") AND isDefined("arguments.project")>
			<cfset variables.project = arguments.project>
		<cfelseif NOT isDefined("variables.project")>
			<cfset variables.project = variables.datasource>
		</cfif>
		
		<cfif NOT isDefined("variables.template") AND isDefined("arguments.template")>
			<cfset variables.template = arguments.template>
		<cfelseif NOT isDefined("variables.template")>
			<cfset variables.template = "reactor">
		</cfif>
		
		<cftry>
			<cfset variables.DbType = trim(stDBMSLookup[stDatasources[variables.datasource].driver])>
			<cfset variables.DbName = trim(stDatasources[variables.datasource].urlmap.database)>
			<cfset variables.DbBuffer = trim(stDatasources[variables.datasource].buffer)>
			<cfcatch>
				<cfthrow type="Application" message="Datasource not found or invalid." 
						 detail="The datasource name, #variables.datasource#, was not found or the DBMS is not of a supported type.">
			</cfcatch> 
		</cftry>
		<cfreturn this>
	</cffunction>
	
	<cffunction name="introspectDB" returntype="metadata" output="No" 
				hint="I introspect the database and update the XML configuration file." >
		<cfargument name="lTables" required="No" type="string" hint="I am a list of tables to generate scaffolding for. Default is to generate code for all tables in the database." />
		
		<cfset var qTables = getTables()>
		<cfset var lValidTables = "">
		<cfset var thisTable = "">
		<cfset var qFields = 0>
		
		<!--- Set up a list of valid tables to be used to generate code --->
		<cfif isDefined("arguments.lTables")>
			<cfloop query="qTables">
				<cfif ListFindNoCase(lTables,qTables.tablename)>
					<cfset lValidTables = ListAppend(lValidTables,qTables.tablename)>
				</cfif>
			</cfloop>
		<cfelse>
			<cfset lValidTables = ValueList(qTables.tablename)>
		</cfif>
		
		<!--- Create object tags for each table --->
		<cfset createObjectTags(lValidTables)>
		
		<!--- Loop over the tables and update the XML for each one to match the database --->
		<cfloop list="#lValidTables#" index="thisTable">
			<cfset qFields = getFields(thisTable)>
			<cfset updateAllFieldPropertiesFromQuery(thisTable,qFields)>
			
			<cfset qParents = getParentRelationships(thisTable)>
			<cfset updateAllParentRelationshipsFromQuery(thisTable,qParents)>
			
		</cfloop>
		
		<!--- Save the updated metadata --->
		<cfset save()>
		
		<cfreturn this>
	</cffunction>
	
	<cffunction name="build" returntype="void" output="No" 
				hint="I build the code using the provided templates.">
		<cfargument name="cftemplate" type="any" required="Yes" 
					hint="I am the cftemplate that will be used to generate the code."/>
		<cfargument name="template" type="string" required="Yes"
					hint="I am the subdirectory of templates to be used" /> 
		<cfargument name="destinationFilePath" type="string" required="No" default="#GetDirectoryFromPath(GetBaseTemplatePath())#" 
					hint="I am the path to the root of the application where the generated code will be written."/>
		<cfargument name="DBName" type="string" required="No" default="#variables.datasource#" 
					hint="I am the database name which is used to name subdirectories."/>
		<cfargument name="lTables" type="string" required="No" default="#ArrayToList(getTablesFromXML())#" 
					hint="I am a list of tables to be used to generate the code. If blank all tables defined in the XML will be used."/>
		
		<cfset var aTemplateFiles = ArrayNew(1)>
		<cfset var stFileData = structNew()>
		<cfset var i = 0>
		<cfset var thisTable = "">
		<cfset var thisAlias = "">
		
		<cfset setLTables(arguments.lTables)>
		
		<!--- Include the descriptor for the selected set of templates --->
		<!--- <cftry> --->
			<cfinclude template="/scaffolder/templates/#arguments.template#/templateDescriptor.cfm">
			<!--- <cfcatch type="MissingInclude">
				<cfthrow type="Template_Descriptor_Not_Found" message="The selected scaffolding template descriptor file '/templates/#arguments.template#/templateDescriptor.cfm' was not found." detail="#cfcatch.detail#">
			</cfcatch>
			<cfcatch type="Any">
				<cfthrow type="Template_Descriptor_Invalid" message="An error occured when executing the selected scaffolding template descriptor file. #cfcatch.message#" detail="An error occured when executing the selected scaffolding template descriptor file. #cfcatch.detail#">
			</cfcatch> --->
		<!--- </cftry> --->
		
		<!--- for each of the templates in the template description array --->
		<cfloop index="i" from="1" to="#ArrayLen(aTemplateFiles)#">
			<cfif aTemplateFiles[i].perObject>
				<!--- We have to generate one of these for each table --->
				<cfloop list="#arguments.lTables#" index="thisTable">
					<!--- Find the alias we are going to use to name our files --->
					<cfset setSelectedTable(thisTable)>
					<cfset thisAlias = getSelectedTableAlias()>
					
					<!--- Work out if the generated filename depends on the object alias --->
					<cfif aTemplateFiles[i].useAliasInName><cfset aliasInName = thisAlias><cfelse><cfset aliasInName = ""></cfif>
					<!--- Generate the code --->
					<cfif aTemplateFiles[i].suffix IS "cfc">
						<cfset cftemplate.generateScript("#arguments.template#/#aTemplateFiles[i].templateFile#.#aTemplateFiles[i].suffix#",this,"#aTemplateFiles[i].MVCpath##aliasInName##aTemplateFiles[i].outputFile#.#aTemplateFiles[i].suffix#",aTemplateFiles[i].inPlace,aTemplateFiles[i].overwrite)>
					<cfelse>
						<cfset cftemplate.generateScript("#arguments.template#/#aTemplateFiles[i].templateFile#.#aTemplateFiles[i].suffix#",this,"#aTemplateFiles[i].MVCpath##aTemplateFiles[i].outputFile##aliasInName#.#aTemplateFiles[i].suffix#",aTemplateFiles[i].inPlace,aTemplateFiles[i].overwrite)>
					</cfif>
				</cfloop>
			<cfelse>
				<!--- We only have to generate one of these for each application --->
				<cfset cftemplate.generateScript("#arguments.template#/#aTemplateFiles[i].templateFile#.#aTemplateFiles[i].suffix#",this,"#aTemplateFiles[i].MVCpath##aTemplateFiles[i].outputFile#.#aTemplateFiles[i].suffix#",aTemplateFiles[i].inPlace,aTemplateFiles[i].overwrite)>
			</cfif>
		</cfloop>
	</cffunction>
	
<!--- *** 					*** --->
<!--- *** Utility Functions *** --->
<!--- *** 					*** --->

	<cffunction name="cleanLabelText" returntype="string" output="No" 
				hint="I process a field name to make a text label for the field by replacing any underscores with a space and putting spaces in front of Camel case words.">
		<cfargument name="text" required="Yes" type="string">
		
		<cfset var local = StructNew()>
		
		<!--- Replace underscores with spaces --->
		<cfset local.text = replace(arguments.text,"_"," ","all")>
		<!--- Look for uppercase letters in camel case names --->
		<cfset local.out = uCase(left(local.text,1))>
		<cfset local.charCount = len(local.text)>
		<cfloop index="local.i" from="2" to="#local.charCount#">
			<cfset local.value = asc(Mid(Text,local.i,1))>
			<cfset local.prev  = asc(Mid(Text,local.i-1,1))>
			<!--- Is this an uppercase character ? --->
			<cfif (local.value GE 65 AND local.value LE 90 AND local.prev GE 97 AND local.prev LE 122)>
				<!--- The character is uppercase so add a space --->
				<cfset local.out = local.out & " " & Mid(local.Text,local.i,1)>
			<cfelse>
				<cfset local.out = local.out & Mid(local.Text,local.i,1)>
			</cfif>
		</cfloop>
		
		<cfreturn local.out>
	</cffunction>
	
	<cffunction name="ArrayConcat" access="private" returntype="array" output="No" hint="I concatenate two arrays.">
		<cfargument name="a1" type="array" />
		<cfargument name="a2" type="array" />
		
		<cfset var i=1>
		<cfscript>
			if ((NOT IsArray(a1)) OR (NOT IsArray(a2))) {
				writeoutput("Error in <Code>ArrayConcat()</code>! Correct usage: ArrayConcat(<I>Array1</I>, <I>Array2</I>) -- Concatenates Array2 to the end of Array1");
				return 0;
			}
			for (i=1;i LTE ArrayLen(a2);i=i+1) {
				ArrayAppend(a1, Duplicate(a2[i]));
			}
		</cfscript>
		<cfreturn a1>
	</cffunction>
	
<!--- *** 							 *** --->
<!--- *** ColdFusion Admin Functions *** --->
<!--- *** 							 *** --->

	<cffunction name="GetAllDatasources" returntype="struct" output="No" 
				hint="I get a structure of all the available datasources.">
		<cfset var factory = 0>
		<cfset var dsService = 0>
		<cfset var stDatasources = 0>
		
		<!--- Get CF "factory" --->
		<cfobject action="CREATE" type="JAVA" class="coldfusion.server.ServiceFactory" name="factory">
		<!--- Get datasource service --->
		<cfset dsService=factory.getDataSourceService()>
		
		<cfset stDatasources = dsService.getDatasources()>
		
		<cfreturn stDatasources>
	</cffunction>
	
	<cffunction name="GetSupportedDatasourcesAsQuery" returntype="query" output="No" 
				hint="I get a recordset of the supported datasources.">
		<cfset var stDatasources = GetAllDatasources()>
		<cfset var qRawDatasources = QueryNew("Datasourcename,DBName,Driver,Buffer")>
		<cfset var thisDSN = 0>
		<cfset var qDatasources = 0>
		<cfset var stDBMSLookup = getSupportedDBMS()>
		
		<cfloop collection="#stDatasources#" item="thisDSN">
			<cfif structKeyExists(stDBMSLookup,stDatasources[thisDSN].driver)>
				<cfset QueryAddRow(qRawDatasources)>
				<cfset QuerySetCell(qRawDatasources,"Datasourcename",stDatasources[thisDSN].name)>
				<cfset QuerySetCell(qRawDatasources,"DBName",stDatasources[thisDSN].urlmap.database)>
				<cfset QuerySetCell(qRawDatasources,"Driver",stDBMSLookup[stDatasources[thisDSN].driver])>
				<cfset QuerySetCell(qRawDatasources,"Buffer",stDatasources[thisDSN].buffer)>
			</cfif>
		</cfloop>
		
		<cfquery name="qDatasources" dbtype="query">
			SELECT 	[Datasourcename],
					[DBName],
					[Driver],
					[Buffer]
			FROM 	qRawDatasources
			ORDER BY [Driver] ASC, [Datasourcename] ASC
		</cfquery>

		<cfreturn qDatasources>
	</cffunction>
	
	<cffunction name="getSupportedDBMS" returntype="struct" output="No" 
				hint="I create a look up structure of the supported DBMS">
		<cfset var stDBMSLookup = structNew()>
		<cfscript>
			// Set up a look up table for the dbms type.
			// The key is the value for the driver returned by the factory and the value is the name used by the scaffolding.
			stDBMSLookup["MSSQLServer"] = "mssql";
			//stDBMSLookup["mysql4"] = "mysql4";
			//stDBMSLookup["mysql"] = "mysql";
			//stDBMSLookup["db2"] = "db2";
			//stDBMSLookup["oracle"] = "oracle";
			//stDBMSLookup["oraclerdb"] = "oraclerdb";
			//stDBMSLookup["postgres"] = "postgres";
		</cfscript>
		<cfreturn stDBMSLookup>
	</cffunction>

<!--- 										 --->
<!--- *** DBMS Introspection Functions   *** --->
<!--- 										 --->

	<cffunction name="getTables" returntype="query" output="No" 
				hint="I get a recordset of tables available from the selected datasource">
		
		<!--- SQL Server version of the code --->
		<cfif getDbType() IS NOT "">
			<cfset qTables = Evaluate("getTables#getDbType()#()")>
		<cfelse>
			<!--- TODO: Add equivalent SQL for other DBMS --->
			<cfthrow type="Application" message="DBMS #attributes.type# is not yet supported.">
		</cfif>
		
		<!--- NOTE Returned query requires the following fields:
			TableName
		 --->
		
		<cfreturn qTables>
	</cffunction>
	
	<cffunction name="getProject" returntype="string" output="No" hint="I return the project name.">
		<cfreturn variables.project>
	</cffunction>
	
	<cffunction name="getTemplate" returntype="string" output="No" hint="I return the Template Type.">
		<cfreturn variables.template>
	</cffunction>
	
	<cffunction name="getDbName" returntype="string" output="No" hint="I return the DBMS Name">
		<cfreturn variables.DbName>
	</cffunction>
	
	<cffunction name="getDbType" returntype="string" output="No" hint="I return the DBMS Type">
		<cfreturn variables.DbType>
	</cffunction>
	
	<cffunction name="getBuffer" returntype="string" output="No" hint="I return the DBMS Driver Buffer Size">
		<cfreturn variables.DbBuffer>
	</cffunction>
	
	<cffunction name="getDatasource" returntype="string" output="No" hint="I return the Datasource Name.">
		<cfreturn variables.datasource>
	</cffunction>
	
	<cffunction name="getUsername" returntype="string" output="No" hint="I return the DBMS Username">
		<cfreturn variables.username>
	</cffunction>
	
	<cffunction name="getPassword" returntype="string" output="No" hint="I return the DBMS Password">
		<cfreturn variables.password>
	</cffunction>
	
	<cffunction name="getTablesMSSQL" returntype="query" output="No" hint="I get a recordset of tables available from the selected datasource">
		<cfset var qTables = 0>
		
		<cfquery name="qTables" datasource="#getDatasource()#" username="#getUsername()#" password="#getPassword()#">
			SELECT 	Name AS tableName,
					Type AS tableType
			FROM 	SYSOBJECTS
			WHERE 	OBJECTPROPERTY(ID, N'IsUserTable') = 1
				AND Name != 'dtproperties'
		</cfquery>
		
		<cfreturn qTables>
	</cffunction>
	
	<cffunction name="getFields" returntype="query" output="No" 
				hint="I return a query containing the fields of the selected table.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="datasource" required="No" default="#variables.datasource#" type="string" hint="I am a datasource name.">
		<cfargument name="username" required="No" default="#variables.username#" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="#variables.password#" type="string" hint="I am the password.">
		
		<cfset var qFields = 0>
		<cfset var stDatasources = GetAllDatasources()>
		<cfset var stDBMSLookup = getSupportedDBMS()>
		<cfset var DBType = stDBMSLookup[stDatasources[arguments.datasource].driver]>
		
		<cfset qFields = Evaluate("getFields#DBType#(arguments.tableName,arguments.datasource,arguments.username,arguments.password)")>
		
		<!--- NOTE Returned query requires the following fields:
			Column_Name,
			Nullable,
			Length,
			Type_Name,
			Ordinal_Position,
			Key_Seq
		 --->
		<cfreturn qFields>
	</cffunction>
	
	<cffunction name="getFieldsMSSQL" returntype="query" output="No" 
				hint="I return a query containing the fields of the selected MSSQL table.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="datasource" required="Yes" type="string" hint="I am a datasource name.">
		<cfargument name="username" required="No" default="" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="" type="string" hint="I am the password.">
		
		<!--- Local variables --->
		<cfset var qFieldsRaw = 0>
		<cfset var qPKFields = 0>
		<cfset var qFields = 0>
		<cfset var lPKFields = "">
		
		<cfquery name="qFieldsRaw" datasource="#arguments.datasource#" username="#arguments.username#" password="#arguments.password#">
			sp_columns '#arguments.tableName#'
		</cfquery>
		
		<cfquery name="qPKFields" datasource="#arguments.datasource#" username="#arguments.username#" password="#arguments.password#">
			sp_pkeys '#arguments.tableName#'
		</cfquery>
		
		<cfquery name="qSpecialFields" datasource="#arguments.datasource#" username="#arguments.username#" password="#arguments.password#">
			sp_special_columns '#arguments.tableName#'
		</cfquery>
		
		<cfset lPKFields = QuotedValueList(qPKFields.Column_Name)>
		
		<cfquery name="qFields" dbtype="query">
			SELECT 	qFieldsRaw.Column_Name,
					Nullable,
					Length,
					Type_Name,
					Ordinal_Position,
					Key_Seq
			FROM 	qFieldsRaw, qPKFields
				WHERE qFieldsRaw.Column_Name = qPKFields.Column_Name
				
			  UNION 
			
			SELECT 	Column_Name,
					Nullable,
					Length,
					Type_Name,
					Ordinal_Position,
					0
			FROM 	qFieldsRaw
				WHERE 0=0
				<cfloop list="#lPKFields#" index="thisField">
				AND NOT qFieldsRaw.Column_Name = #preservesinglequotes(thisField)#</cfloop>
			
			ORDER BY Ordinal_Position
		</cfquery>
		<cfreturn qFields>
	</cffunction>
	
	<cffunction name="getParentRelationships" returntype="query" output="No" 
				hint="I return a query containing the parent relationships for the selected table.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="datasource" required="No" default="#variables.datasource#" type="string" hint="I am a datasource name.">
		<cfargument name="username" required="No" default="#variables.username#" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="#variables.password#" type="string" hint="I am the password.">
		
		<cfset var stDatasources = GetAllDatasources()>
		<cfset var stDBMSLookup = getSupportedDBMS()>
		<cfset var DBType = stDBMSLookup[stDatasources[arguments.datasource].driver]>
		
		<cfset var qFKFields = Evaluate("getParentRelationships#DBType#(arguments.datasource,arguments.tableName,arguments.username,arguments.password)")>
		
		<!--- NOTE Returned query requires the following fields:
			
		 --->
		<cfreturn qFKFields>
		
	</cffunction>
	
	<cffunction name="getParentRelationshipsMSSQL" returntype="query" output="No" 
				hint="I return a query containing the parent relationships for the selected table.">
		<cfargument name="datasource" required="Yes" type="string" hint="I am a datasource name.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="username" required="No" default="" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="" type="string" hint="I am the password.">
		
		<cfset var qPKFields=0>
		
		<cfquery name="qFKFields" datasource="#arguments.datasource#" username="#arguments.username#" password="#arguments.password#">
			sp_fkeys @fktable_name = '#arguments.tableName#'
		</cfquery>
		
		<cfreturn qFKFields>
		
	</cffunction>
	
	<cffunction name="getChildRelationships" returntype="query" output="No" 
				hint="I return a query containing the child relationships for the selected table.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="datasource" required="No" default="#variables.datasource#" type="string" hint="I am a datasource name.">
		<cfargument name="username" required="No" default="#variables.username#" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="#variables.password#" type="string" hint="I am the password.">
		
		<cfset var stDatasources = GetAllDatasources()>
		<cfset var stDBMSLookup = getSupportedDBMS()>
		<cfset var DBType = stDBMSLookup[stDatasources[arguments.datasource].driver]>
		
		<cfset var qPKFields = Evaluate("getChildRelationships#DBType#(arguments.datasource,arguments.tableName,arguments.username,arguments.password)")>
		
		<!--- NOTE Returned query requires the following fields:
			
		 --->
		<cfreturn qPKFields>
		
	</cffunction>
	
	<cffunction name="getChildRelationshipsMSSQL" returntype="query" output="No" 
				hint="I return a query containing the child relationships for the selected table.">
		<cfargument name="datasource" required="Yes" type="string" hint="I am a datasource name.">
		<cfargument name="tableName" type="string" required="Yes">
		<cfargument name="username" required="No" default="" type="string" hint="I am the username.">
		<cfargument name="password" required="No" default="" type="string" hint="I am the password.">
		<cfset var qPKFields=0>
		
		<cfquery name="qPKFields" datasource="#arguments.datasource#" username="#arguments.username#" password="#arguments.password#">
			sp_fkeys @pktable_name = '#arguments.tableName#'
		</cfquery>
		
		<cfreturn qPKFields>
		
	</cffunction>
	
<!--- *** 													  **** --->
<!--- *** The following functions Read and write the XML file **** --->
<!--- *** 													  **** --->
	
	<cffunction name="read" returntype="any" output="No" hint="I read the XML Metadata file and populate the local copy.">
		<cfargument name="configFilePath" default="#variables.configFilePath#" required="No" >
		
		<cfset var configFile = "">
		<cfset var theError = "">
		
		<cfif fileexists(arguments.configFilePath)>
			<cftry>
				<cffile action="READ" file="#arguments.configFilePath#" variable="configFile">
				<cfcatch>
					<cfset theError = structNew()>
					<cfset theError.message="The XML config file was not found.">
					<cfset theError.detail="The XML config file (#arguments.configFilePath#) was not found.">
					<cfthrow type="Application" message="#theError.message#" detail="#theError.message#">
				</cfcatch>
			</cftry>
			<cftry>
				<cfset variables.xScaffoldingConfig = xmlParse(configFile)>
				<cfcatch>
					<cfset theError = structNew()>
					<cfset theError.message="The XML config file is not valid.">
					<cfset theError.detail="The XML config file (#arguments.configFilePath#) is not valid.">
					<cfthrow type="Application" message="#theError.message#" detail="#theError.message#">
				</cfcatch>
			</cftry>
			
			<cfif isDefined("variables.xScaffoldingConfig.scaffolding.config.project")>
				<cfset variables.project = variables.xScaffoldingConfig.scaffolding.config.project.XMLAttributes.value>
			</cfif>
			<cfif isDefined("variables.xScaffoldingConfig.scaffolding.config.template")>
				<cfset variables.template = variables.xScaffoldingConfig.scaffolding.config.template.XMLAttributes.value>
			</cfif>
			<cfif isDefined("variables.xScaffoldingConfig.scaffolding.config.dsn")>
				<cfset variables.datasource = variables.xScaffoldingConfig.scaffolding.config.dsn.XMLAttributes.value>
			</cfif>
			<cfif isDefined("variables.xScaffoldingConfig.scaffolding.config.username")>
				<cfset variables.username = variables.xScaffoldingConfig.scaffolding.config.username.XMLAttributes.value>
			</cfif>
			<cfif isDefined("variables.xScaffoldingConfig.scaffolding.config.password")>
				<cfset variables.password = variables.xScaffoldingConfig.scaffolding.config.password.XMLAttributes.value>
			</cfif>
		<cfelse>
			<cfset variables.xScaffoldingConfig = xmlNew()>
			<cfset variables.xScaffoldingConfig.scaffolding = xmlElemNew(variables.xScaffoldingConfig,"scaffolding")>
			<cfset variables.xScaffoldingConfig.scaffolding.config = xmlElemNew(variables.xScaffoldingConfig,"config")>
			<cfset variables.xScaffoldingConfig.scaffolding.objects = xmlElemNew(variables.xScaffoldingConfig,"objects")>
		</cfif>
		
		<cfreturn variables.xScaffoldingConfig>
	</cffunction>

	<cffunction name="save" returntype="boolean" output="No" hint="I save the XML Metadata.">
		<cfargument name="configFilePath" required="No" default="#variables.configFilePath#">
		
		<cfset var outstring = toString(variables.xScaffoldingConfig)>
		
		<cfif arguments.configFilePath IS "">
			<cfthrow type="Fusebox_Scaffolding_Config" message="Undefined configuration file path." detail="The configuration file path is empty.">
		</cfif>
		
		<!--- Format the XML nicely --->
		<cfset outstring = REReplace(outstring,">[[:space:]]*<config",">#chr(13)##chr(9)#<config","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*</config>",">#chr(13)##chr(9)#</config>","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<objects",">#chr(13)##chr(13)##chr(9)#<objects","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*</objects>",">#chr(13)##chr(9)#</objects>","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<object ",">#chr(13)##chr(13)##chr(9)##chr(9)#<object ","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*</object>",">#chr(13)##chr(9)##chr(9)#</object>","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<manyTo",">#chr(13)##chr(9)##chr(9)##chr(9)#<manyTo","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*</manyTo",">#chr(13)##chr(9)##chr(9)##chr(9)#</manyTo","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<oneTo",">#chr(13)##chr(9)##chr(9)##chr(9)#<oneTo","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*</oneTo",">#chr(13)##chr(9)##chr(9)##chr(9)#</oneTo","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<relate",">#chr(13)##chr(9)##chr(9)##chr(9)##chr(9)#<relate","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<link",">#chr(13)##chr(9)##chr(9)##chr(9)##chr(9)#<link","all") >
		<cfset outstring = REReplace(outstring,">[[:space:]]*<field",">#chr(13)##chr(9)##chr(9)##chr(9)#<field","all") >
		
		<cffile action="WRITE" 
				file="#arguments.configFilePath#" 
				output="#outstring#" 
				addnewline="Yes">
		
		<cfreturn true>
	</cffunction>
	
	<!--- *** Create or update a struct of configuration entries in the XML *** --->
	<cffunction name="setConfig" returntype="boolean" output="No" hint="I set up the basic configuration information in the XML.">
		<cfargument name="stFormData" type="struct" required="Yes" hint="I accept a structure containing the basic configuration information from the form.">
		<cfargument name="qDatasources" required="Yes" type="query" hint="I am a recordset of valid datasources.">
		<cfargument name="stDBMSLookup" required="Yes" type="struct" hint="I am a structure of supported DBMS.">
		
		<cfset var fieldlist="project,dsn,type,mode,mapping,username,password,template">
		
		<!--- Get the DBMS Type from the database --->
		<cfquery name="qCheckDatabaseType" dbtype="query">
			SELECT Driver FROM arguments.qDatasources WHERE Datasourcename = '#arguments.stFormData.dsn#'
		</cfquery>
		<cfif qCheckDatabaseType.recordcount GT 0>
			<cfset arguments.stFormData.type = arguments.stDBMSLookup[qCheckDatabase.Driver]>
		</cfif>
		
		<!--- Since we are making changes lets put it into development mode --->
		<cfset arguments.stFormData.mode ="development">
		
		<!--- Set a default mapping for each circuit --->
		<cfif NOT isDefined("variables.xScaffoldingConfig.scaffolding.config.mPath")>
			<cfparam name="stFormData.mMapping" default="#getDirectoryFromPath(variables.configFilePath)#model\">
		</cfif>
		<cfif NOT isDefined("variables.xScaffoldingConfig.scaffolding.config.vPath")>
			<cfparam name="stFormData.vMapping" default="#getDirectoryFromPath(variables.configFilePath)#view\">
		</cfif>
		<cfif NOT isDefined("variables.xScaffoldingConfig.scaffolding.config.cPath")>
			<cfparam name="stFormData.cMapping" default="#getDirectoryFromPath(variables.configFilePath)#controller\">
		</cfif>
		
		<!--- Loop over the fields and update the XML --->
		<cfloop list="#fieldlist#" index="thisField">
			<!--- Look to see if the field is in stFormData --->
			<cfif structKeyExists(stFormData,thisField)>
				<!--- Update the XML document object element or create a new one --->
				<cfif NOT structKeyExists(variables.xScaffoldingConfig.scaffolding.config,thisField)>
					<cfset variables.xScaffoldingConfig.scaffolding.config[thisField] = XmlElemNew(xScaffoldingConfig,thisField)>
				</cfif>
				<cfset variables.xScaffoldingConfig.scaffolding.config[thisField].XmlAttributes.value = stFormData[thisField]>
			</cfif>
		</cfloop>
		<cfreturn true>
	</cffunction>
	
	<cffunction name="getConfig" returntype="struct" output="No" hint="I get the basic configuration information from the XML.">
		<cfset var fieldlist="project,dsn,type,mode,mPath,vPath,cPath,username,password,template">
		<cfset var stConfigData = structNew()>
		
		<cfif NOT isDefined("variables.configFilePath")>
			<cfset variables.configFilePath = getDirectoryFromPath(GetBaseTemplatePath())>
		</cfif>
		
		<cfloop list="#fieldlist#" index="thisField">
			<cfif structKeyExists(variables.xScaffoldingConfig.scaffolding.config,thisField)>
				<cfset stConfigData[thisField] = variables.xScaffoldingConfig.scaffolding.config[thisField].XmlAttributes.value>
			<cfelseif thisField IS "mPath">
				<cfset stConfigData[thisField] = "#getDirectoryFromPath(variables.configFilePath)#model\">
			<cfelseif thisField IS "vPath">
				<cfset stConfigData[thisField] = "#getDirectoryFromPath(variables.configFilePath)#view\">
			<cfelseif thisField IS "cPath">
				<cfset stConfigData[thisField] = "#getDirectoryFromPath(variables.configFilePath)#controller\">
			<cfelse>
				<cfset stConfigData[thisField] = "">
			</cfif>
		</cfloop>
		<cfreturn stConfigData>
	</cffunction>
	
	<!--- *** Find and create object entries in the XML *** --->
	<cffunction name="getObjectPosition" returntype="numeric" output="No" 
				hint="I look for an object by name or alias in the XML and return its position, if not found zero is returned.">
		<cfargument name="name" type="string" required="No" default="" >
		<cfargument name="alias" type="string" required="No" default="" >
		
		<!--- Get the array of objects --->
		<cfset var aObjects = variables.xScaffoldingConfig.scaffolding.objects.xmlChildren>
		<cfset var objectCount = ArrayLen(aObjects)>
		<cfset var thisIndex = 0>
		<cfset var i = 0>
		
		<!--- Look for the object in the array --->
		<cfloop from="1" to="#objectCount#" index="i">
			<cfif arguments.name IS NOT "" AND aObjects[i].XmlAttributes.name IS arguments.name>
				<cfset thisIndex = i>
				<cfbreak>
			<cfelseif arguments.alias IS NOT "" AND aObjects[i].XmlAttributes.alias IS arguments.alias>
				<cfset thisIndex = i>
				<cfbreak>
			</cfif>
		</cfloop>
		
		<cfreturn thisIndex>
	</cffunction>
	
	<cffunction name="createObjectTag" returntype="numeric" output="No" 
				hint="I check if an object exists, if not I create the object in the XML and return its position.">
		<cfargument name="name" type="string" required="Yes" >
		<cfargument name="alias" type="string" required="No" default="#arguments.name#" >
		<cfargument name="label" type="string" required="No" default="#arguments.alias#" >
		
		<!--- See if the object already exists --->
		<cfset var thisIndex = getObjectPosition(name=arguments.name)>
		
		<!--- If the object wasn't found then add it and set the name, alias and label --->	
		<cfif thisIndex IS 0>
			<cfset ArrayAppend(xScaffoldingConfig.scaffolding.objects.XmlChildren,XmlElemNew(xScaffoldingConfig,"object"))>
			<!--- New objects allways get added at the end of the array --->
			<cfset thisIndex = ArrayLen(variables.xScaffoldingConfig.scaffolding.objects.xmlChildren)>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[thisIndex].XmlAttributes["name"] = arguments.name>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[thisIndex].XmlAttributes["alias"] = arguments.alias>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[thisIndex].XmlAttributes["label"] = arguments.label>
		</cfif>
		
		<cfreturn thisIndex>
	</cffunction>
	
	<cffunction name="createObjectTags" returntype="void" output="No" 
				hint="I create an object in the XML for each table in the list.">
		<cfargument name="lTables" type="string" required="Yes" hint="I am the list of tables.">
		<cfset var thisTable = "">
		<cfset var thisAlias = "">
		
		<cfloop list="#arguments.lTables#" index="thisTable">
			<!--- If the table name starts with tbl remove it --->
			<cfif left(thisTable,3) IS "tbl"><cfset thisAlias = removeChars(thisTable,1,3)><cfelse><cfset thisAlias = thisTable></cfif>
			<cfset createObjectTag(thisTable,thisAlias,cleanLabelText(thisAlias))>
		</cfloop>
	</cffunction>
	
	<!--- *** Find and create the fields in the XML within an object *** --->
	<cffunction name="getFieldPosition" returntype="numeric" output="No" 
				hint="I look up the field by name or alias and return its position within the object, if not found return zero.">
		<cfargument name="objectIndex" type="numeric" required="Yes" >
		<cfargument name="name" type="string" required="No" default="">
		<cfargument name="alias" type="string" required="No" default="">
		
		<cfset var aObjectChildren = "">
		<cfset var objectChildCount =  0>
		<cfset var j = 0>
		<cfset var thisFieldIndex = 0>
		
		<cfif isDefined("variables.xScaffoldingConfig.scaffolding.objects.object") AND arguments.objectIndex GT 0>
			<cfset aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren>
			<cfset objectChildCount =  ArrayLen(aObjectChildren)>
		<cfelse>
			<cfreturn 0>
		</cfif>
		
		<!--- Look for the field in the array --->
		<cfloop from="1" to="#objectChildCount#" index="j">
			<!--- <cfoutput>#aObjectChildren[j].XmlName# - #aObjectChildren[j].XmlAttributes.name#<br /></cfoutput> --->
			<cfif arguments.name IS NOT ""
			  AND aObjectChildren[j].XmlName IS "field"
			  AND aObjectChildren[j].XmlAttributes.name IS arguments.name>
				<cfset thisFieldIndex = j>
				<cfbreak>
			<cfelseif arguments.alias IS NOT ""
			  AND aObjectChildren[j].XmlName IS "field"
			  AND structKeyExists(aObjectChildren[j].XmlAttributes,"alias")
			  AND aObjectChildren[j].XmlAttributes.alias IS arguments.alias>
				<cfset thisFieldIndex = j>
				<cfbreak>
			</cfif>
		</cfloop>
		
		<cfreturn thisFieldIndex>
	</cffunction>
	
	<cffunction name="createFieldTag" returntype="numeric" output="No" 
				hint="I find or create a single field within an object in the XML and return its position.">
		<cfargument name="objectIndex" required="Yes" type="numeric">
		<cfargument name="name" type="string" required="Yes" >
		<cfargument name="alias" type="string" required="No" default="#arguments.name#">
		<cfargument name="label" type="string" required="No" default="#cleanLabelText(arguments.alias)#" >
		
		<cfset var thisFieldIndex = getFieldPosition(objectIndex=arguments.objectIndex,name=arguments.name)>
		<cfset var aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren>
		<cfset var objectChildIndex = ArrayLen(aObjectChildren)>
		
		<!--- If the field isn't there add it --->
		<cfif thisFieldIndex IS 0>
			<cfset ArrayAppend(variables.xScaffoldingConfig.scaffolding.objects.XmlChildren[arguments.objectIndex].XmlChildren,XmlElemNew(xScaffoldingConfig,"field"))>
			<!--- New fields always get added at the end of the array --->
			<cfset objectChildIndex = objectChildIndex + 1>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["name"] = arguments.name>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["alias"] = arguments.alias>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["label"] = arguments.label>
			<cfset thisFieldIndex = objectChildIndex>
		</cfif>
		
		<cfreturn thisFieldIndex>
	</cffunction>
	
	<!--- *** Create or update the other field properties *** --->
	<cffunction name="setAttributeValue" returntype="string" output="No" 
				hint="I update, create or remove the value of an XML attribite.">
		<cfargument name="stProperties" required="Yes" type="struct" 
			hint="I am a pointer to the structure containing the attributes of the XML tag to be updated." >
		<cfargument name="attribute" required="Yes" type="string" hint="Attribute name to be updated.">
		<cfargument name="value" required="Yes" type="any" hint="The new attribute value">
		<cfargument name="rule" required="No" type="string" default="noOverwrite" hint="Rule to be followed eg: Overwrite,noOverwrite.">
		
		<cfif StructKeyExists(arguments.stProperties,arguments.attribute)>
			<cfif arguments.rule DOES NOT CONTAIN "noOverwrite" AND arguments.value IS NOT "" >
				<cfset arguments.stProperties[arguments.attribute] = arguments.value>
			<cfelseif arguments.rule DOES NOT CONTAIN "noOverwrite" AND arguments.value IS "">
				<cfset structDelete(arguments.stProperties,arguments.attribute)>
				<cfreturn "">
			</cfif>
		<cfelse>
			<cfif arguments.value IS NOT "" >
				<cfset arguments.stProperties[arguments.attribute] = arguments.value>
			<cfelse>
				<cfreturn "">
			</cfif>
		</cfif>
		<cfreturn arguments.stProperties[arguments.attribute]>
	</cffunction>
	
	<cffunction name="updateAllFieldPropertiesFromQuery" returntype="void" output="No" 
				hint="I add or update the additional metadata properties for all fields in a table required by the scaffolding.">
		<cfargument name="tableName" required="Yes" type="string" hint="Name of the table.">
		<cfargument name="qTableData" required="Yes" type="query" hint="A Query containing the data for each field.">
		
		<cfset var thisFieldIndex = 0>
		<cfset var stProperties = structNew()>
		
		<!--- Query from MSSQL contains COLUMN_NAME,KEY_SEQ,LENGTH,NULLABLE,ORDINAL_POSITION,TYPE_NAME  --->
		
		<!--- Example of field tag to be created:
		 <field	name="TagId" 
		 		alias="TagId" 
				label="Tag Id" 
				type="integer" 
				formType="Hidden" 
				format="Number(0)" 
				required="false" 
				showOnForm="true" 
				showOnList="true" 
				size="0" 
				sort="1" 
				primaryKeySeq="1" 
				identity="true" 
		 />
		 --->
		
		<!--- Find the object that is being updated --->
		<cfset var objectIndex = getObjectPosition(name=arguments.tableName)>
		<cfset var stType = structNew()>
		
		<!--- Create a type lookup --->
		<cfset stType["bigint"] = "integer">
		<cfset stType["binary"] = "binary">
		<cfset stType["bit"] = "boolean">
		<cfset stType["char"] = "string">
		<cfset stType["datetime"] = "datetime">
		<cfset stType["decimal"] = "number">
		<cfset stType["float"] = "number">
		<cfset stType["image"] = "binary">
		<cfset stType["int"] = "integer">
		<cfset stType["money"] = "number">
		<cfset stType["nchar"] = "string">
		<cfset stType["ntext"] = "string">
		<cfset stType["numeric"] = "number">
		<cfset stType["nvarchar"] = "string">
		<cfset stType["real"] = "number">
		<cfset stType["smalldatetime"] = "datetime">
		<cfset stType["smallint"] = "integer">
		<cfset stType["smallmoney"] = "number">
		<cfset stType["sql_variant"] = "number">
		<cfset stType["text"] = "string">
		<cfset stType["timestamp"] = "datetime">
		<cfset stType["tinyint"] = "integer">
		<cfset stType["uniqueidentifier"] = "string">
		<cfset stType["varbinary"] = "binary">
		<cfset stType["varchar"] = "string">
		
		<!--- Loop over the fields and set the default properties of each --->
		<cfloop query="arguments.qTableData">
			<!--- Find the field tag position or create a new one --->
			<cfset thisFieldIndex = createFieldTag(objectIndex=objectIndex,name=arguments.qTableData.column_name)>
			
			<!--- Set up a pointer to the structure containing the attributes of the field tag in the XML --->
			<cfset stProperties = variables.xScaffoldingConfig.scaffolding.objects.object[objectIndex].XmlChildren[thisFieldIndex].XmlAttributes >
			
			<!--- Update the identity, type and primaryKeySeq attributes, exsiting values are always overwritten --->
			<cfif arguments.qTableData.type_name CONTAINS "identity">
				<cfset setAttributeValue(stProperties,"identity","true","overwrite") >
				<cfset setAttributeValue(stProperties,"type",stType[trim(replace(arguments.qTableData.type_name,"identity",""))],"overwrite")>
			<cfelse>
				<cfset setAttributeValue(stProperties,"type",stType[arguments.qTableData.type_name],"overwrite")>
			</cfif>
			
			<cfset setAttributeValue(stProperties,"primaryKeySeq",arguments.qTableData.key_seq,"overwrite")>
			
			<!--- The values of other XML attribute values never get overwritten --->
			<cfset setAttributeValue(stProperties,"sort",arguments.qTableData.key_seq)>
			<cfset setAttributeValue(stProperties,"showOnList","true")>
			<cfset setAttributeValue(stProperties,"showOnForm","true")>
			<cfif arguments.qTableData.nullable>
				<cfset setAttributeValue(stProperties,"required","false")>
			<cfelse>
				<cfset setAttributeValue(stProperties,"required","true")>
			</cfif>
			<!--- The values of formType, format, size depend on various rules but existing values never get overwritten --->
			<cfif stProperties.type IS "datetime">
				<cfset setAttributeValue(stProperties,"formType","Calendar")>
				<cfset setAttributeValue(stProperties,"format","dd/mmm/yyyy")>
				<cfset setAttributeValue(stProperties,"size","15")>
				<cfset setAttributeValue(stProperties,"maxlength",11)>
			<cfelseif isDefined("stProperties.parent") AND stProperties.parent IS NOT "">
				<cfset setAttributeValue(stProperties,"formType","Dropdown")>
				<cfset setAttributeValue(stProperties,"format","Trim")>
				<cfset setAttributeValue(stProperties,"size","1")>
			<cfelseif stProperties.type IS "boolean">
				<cfset setAttributeValue(stProperties,"formType","Checkbox")>
				<cfset setAttributeValue(stProperties,"format","YesNo")>
				<cfset setAttributeValue(stProperties,"size","0")>
			<cfelseif isDefined("stProperties.identity") AND stProperties.identity>
				<cfset setAttributeValue(stProperties,"formType","Hidden")>
				<cfset setAttributeValue(stProperties,"format","None")>
				<cfset setAttributeValue(stProperties,"size","0")>
			<cfelseif arguments.qTableData.type_name IS "money">
				<cfset setAttributeValue(stProperties,"formType","Text")>
				<cfset setAttributeValue(stProperties,"format","Currency")>
				<cfset setAttributeValue(stProperties,"size","15")>
				<cfset setAttributeValue(stProperties,"maxlength",15)>
			<cfelseif stProperties.type IS "integer">
				<cfset setAttributeValue(stProperties,"formType","Text")>
				<cfset setAttributeValue(stProperties,"format","Integer")>
				<cfset setAttributeValue(stProperties,"size","15")>
				<cfset setAttributeValue(stProperties,"maxlength",15)>
			<cfelseif stProperties.type IS "numeric">
				<cfset setAttributeValue(stProperties,"formType","Text")>
				<cfset setAttributeValue(stProperties,"format","Number(9.99)")>
				<cfset setAttributeValue(stProperties,"size","15")>
				<cfset setAttributeValue(stProperties,"maxlength",15)>
			<cfelseif arguments.qTableData.length gt 200>
				<cfset setAttributeValue(stProperties,"formType","Textarea")>
				<cfset setAttributeValue(stProperties,"format","Trim")>
				<cfset setAttributeValue(stProperties,"size","30x4")>
				<cfset setAttributeValue(stProperties,"maxlength",min(arguments.qTableData.length,getBuffer()))>
			<cfelse>
				<cfset setAttributeValue(stProperties,"formType","Text")>
				<cfset setAttributeValue(stProperties,"format","Trim")>
				<cfset setAttributeValue(stProperties,"size","30")>
				<cfset setAttributeValue(stProperties,"maxlength",min(arguments.qTableData.length,getBuffer()))>
			</cfif>
			
		</cfloop>
		
	</cffunction>
	
	<!--- <cffunction name="updateFieldProperties" returntype="void" 
				hint="I add or update the additional metadata properties for a single field required by the scaffolding.">
		<cfargument name="objectIndex" required="Yes" type="numeric" hint="Position of the object.">
		<cfargument name="fieldName" required="Yes" type="string" hint="Name of the field.">
		<cfargument name="stProperties" required="Yes" type="struct" hint="A struct with the field properties and values to be added.">
		
		<!--- Find the field position --->
		<cfset var thisFieldIndex = getFieldPosition(objectIndex=arguments.objectIndex,fieldName=arguments.fieldName)>
		
		<!--- If the field is not there, create it --->
		<cfif thisFieldIndex IS 0>
			<cfset thisFieldIndex = createFieldTag(objectIndex=arguments.objectIndex,fieldName=arguments.fieldName)>
		</cfif>
		
		<!--- Loop over the properties and update them or remove them --->
		<cfloop collection="#stProperties#" item="thisField">
			<cfif stProperties[thisField] IS "NULL">
				<cfset structDelete(variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[thisFieldIndex].XmlAttributes,thisField,"false")>
			<cfelseif thisField IS NOT "hasOne">
				<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[thisFieldIndex].XmlAttributes[thisField] = stProperties[thisField]>
			</cfif>
		</cfloop>
	</cffunction>
	
	<cffunction name="updateAllFieldProperties" returntype="void" 
				hint="I add or update the additional metadata properties for all fields in a table required by the scaffolding.">
		<cfargument name="tableName" required="Yes" type="string" hint="Name of the table.">
		<cfargument name="aData" required="Yes" type="array" hint="An array of structures containing the data for each field.">

		<cfset var fieldCount = ArrayLen(arguments.aData)>
		
		<!--- Find the object that is being updated --->
		<cfset var objectIndex = getObjectPosition(name=arguments.tableName)>
		
		<!--- Loop over the fields and update them --->
		<cfloop from="1" to="#fieldcount#" index="i">
			<cfset updateFieldProperties(objectIndex=objectIndex,fieldName=aData[i].name,stProperties=aData[i])>
		</cfloop>
			
	</cffunction> --->
	
	<!--- *** Find and create the relationships in the XML within an object *** --->
	<cffunction name="getRelationshipPosition" returntype="numeric" output="No" 
				hint="I look up the relationship by fkName, name or alias and return its position within the object, if not found return zero.">
		<cfargument name="objectIndex" type="numeric" required="Yes" />
		<cfargument name="name" type="string" required="No" default="" hint="I am the alias of the related object."/>
		<cfargument name="alias" type="string" required="No" default="" hint="I am an alternate name for this relationship."/>
		<cfargument name="fkName" type="string" required="No" default="" hint="The name of the foreign key in the database."/>
		<cfargument name="lTypes" type="string" required="No" default="oneToMany,oneToOne,manyToOne,oneToOne" hint="List of the types of realtionship to search for. Default is all." />
		
		<cfset var aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren>
		<cfset var objectChildCount =  ArrayLen(aObjectChildren)>
		<cfset var j = 0>
		<cfset var thisFieldIndex = 0>
		
		<!--- Look for the relationship in the array --->
		<cfloop from="1" to="#objectChildCount#" index="j">
			<cfif arguments.fkName IS NOT ""
			  AND ListFindNoCase(lTypes,aObjectChildren[j].XmlName)
			  AND structKeyExists(aObjectChildren[j].XmlAttributes,"fkName")
			  AND aObjectChildren[j].XmlAttributes.fkName IS arguments.fkName>
				<cfset thisFieldIndex = j>
				<cfbreak>
			<cfelseif arguments.name IS NOT "" AND arguments.fkName IS ""
			  AND ListFindNoCase(lTypes,aObjectChildren[j].XmlName)
			  AND aObjectChildren[j].XmlAttributes.name IS arguments.name>
				<cfset thisFieldIndex = j>
				<cfbreak>
			<cfelseif arguments.alias IS NOT ""
			  AND ListFindNoCase(lTypes,aObjectChildren[j].XmlName)
			  AND structKeyExists(aObjectChildren[j].XmlAttributes,"alias")
			  AND aObjectChildren[j].XmlAttributes.alias IS arguments.alias>
				<cfset thisFieldIndex = j>
				<cfbreak>
			</cfif>
		</cfloop>
		
		<cfreturn thisFieldIndex>
	</cffunction>
	
	<cffunction name="createRelationshipTag" returntype="numeric" output="No" 
				hint="I find or create a single relationship within an object in the XML and return its position.">
		<cfargument name="objectIndex" required="Yes" type="numeric" />
		<cfargument name="name" type="string" required="Yes" />
		<cfargument name="type" type="string" required="Yes" />
		<cfargument name="alias" type="string" required="No" default="" />
		<cfargument name="fkName" type="string" required="No" default="" hint="The name of the foreign key in the database."/>
		<cfargument name="sharedKey" type="string" required="No" default="" hint=""/>
		<cfargument name="label" type="string" required="No" default="#cleanLabelText(arguments.alias)#" />
		
		<cfset var j = 0>
		<cfset var thisRelationshipIndex = getRelationshipPosition(objectIndex=arguments.objectIndex,name=arguments.name,fkName=arguments.fkName)>
		<cfset var aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren>
		<cfset var objectChildIndex = 1>
		<cfset var objectChildCount =  ArrayLen(aObjectChildren)>
		
		<!--- If the relationship isn't there add it --->
		<cfif thisRelationshipIndex IS 0>
			<!--- Find the highest position for a relationship of this type --->
			<cfloop from="1" to="#objectChildCount#" index="j">
				<cfif aObjectChildren[j].XmlName IS arguments.type>
					<cfset objectChildIndex = j + 1>
				<cfelseif aObjectChildren[j].XmlName IS "field" AND objectChildIndex IS 0 >
					<cfset objectChildIndex = j>
					<cfbreak>
				</cfif>
			</cfloop>
			
			<!--- New relationships get inserted at a suitable position --->
			<cfset ArrayInsertAt(variables.xScaffoldingConfig.scaffolding.objects.XmlChildren[arguments.objectIndex].XmlChildren,objectChildIndex,XmlElemNew(xScaffoldingConfig,type))>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["name"] = arguments.name>
			<cfif arguments.alias IS NOT "">
				<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["alias"] = arguments.alias>
			</cfif>
			<cfif arguments.label IS NOT "">
				<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["label"] = arguments.label>
			</cfif>
			<cfif arguments.fkName IS NOT "">
				<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["fkName"] = arguments.fkName>
			</cfif>
			<cfif arguments.sharedKey IS NOT "">
				<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[objectChildIndex].XmlAttributes["sharedKey"] = arguments.sharedKey>
			</cfif>
			<cfset thisRelationshipIndex = objectChildIndex>
		</cfif>
		
		<cfreturn thisRelationshipIndex>
	</cffunction>
	
	<cffunction name="getRelateOrLinkPosition" returntype="numeric" output="No" 
				hint="I look up the relate or link tag by its from and to values and return its position within the object, if not found return zero.">
		<cfargument name="objectIndex" type="numeric" required="Yes" />
		<cfargument name="relationshipIndex" type="numeric" required="Yes" />
		<cfargument name="from" type="string" required="Yes" />
		<cfargument name="to" type="string" required="Yes" />
		<cfargument name="lTypes" type="string" required="No" default="relate,link" hint="List of the types of tag to search for. Default is all." />
		
		<cfset var aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[arguments.relationshipIndex].XmlChildren>
		<cfset var objectChildCount =  ArrayLen(aObjectChildren)>
		<cfset var j = 0>
		<cfset var thisTagIndex = 0>
		
		<!--- Look for the tag in the array --->
		<cfloop from="1" to="#objectChildCount#" index="j">
			<cfif arguments.from IS NOT "" AND arguments.to IS NOT ""
			  AND ListFindNoCase(lTypes,aObjectChildren[j].XmlName)
			  AND aObjectChildren[j].XmlAttributes.from IS arguments.from
			  AND aObjectChildren[j].XmlAttributes.to IS arguments.to>
				<cfset thisTagIndex = j>
				<cfbreak>
			</cfif>
		</cfloop>
		
		<cfreturn thisTagIndex>
	</cffunction>
	
	<cffunction name="createRelateOrLinkTag" returntype="numeric" output="No" 
				hint="I find or create a single relate or link tag within an object in the XML and return its position.">
		<cfargument name="objectIndex" required="Yes" type="numeric" />
		<cfargument name="relationshipIndex" type="numeric" required="Yes" />
		<cfargument name="from" type="string" required="Yes" />
		<cfargument name="to" type="string" required="Yes" />
		<cfargument name="type" type="string" required="Yes" />
		
		<cfset var j = 0>
		<cfset var thisTagIndex = getRelateOrLinkPosition(objectIndex=arguments.objectIndex,relationshipIndex=arguments.relationshipIndex,from=arguments.from,to=arguments.to,ltypes=arguments.type)>
		<cfset var aObjectChildren = variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[arguments.relationshipIndex].XmlChildren>
		<cfset var objectChildIndex =  ArrayLen(aObjectChildren)>
		
		<!--- If the tag isn't there add it --->
		<cfif thisTagIndex IS 0>
			<cfset objectChildIndex = objectChildIndex + 1>
			<!--- New tags get inserted at the end --->
			<cfset ArrayAppend(variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[arguments.relationshipIndex].XmlChildren,XmlElemNew(xScaffoldingConfig,type))>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[arguments.relationshipIndex].XmlChildren[objectChildIndex].XmlAttributes["from"] = arguments.from>
			<cfset variables.xScaffoldingConfig.scaffolding.objects.object[arguments.objectIndex].XmlChildren[arguments.relationshipIndex].XmlChildren[objectChildIndex].XmlAttributes["to"] = arguments.to>
			<cfset thisTagIndex = objectChildIndex>
		</cfif>
		
		<cfreturn thisTagIndex>
	</cffunction>
	
	<cffunction name="updateAllParentRelationshipsFromQuery" returntype="void" output="No" 
				hint="I find or create the parent relationships within an object in the XML from a query.">
		<cfargument name="tableName" required="Yes" type="string" hint="Name of the table.">
		<cfargument name="qRelationshipData" required="Yes" type="query" hint="A Query containing the data for each relationship.">
		
		<!--- Find the object tag position --->
		<cfset var objectIndex = getObjectPosition(name=arguments.tableName)>
		
		<!--- Find the manyToOne tag position or create a new one and its associated relate tags --->
		<cfloop query="arguments.qRelationshipData">
			<cfset manyToOneIndex = createRelationshipTag(objectIndex=objectIndex,name=arguments.qRelationshipData.pktable_name,fkname=arguments.qRelationshipData.fk_name,type="manyToOne")>
			<cfset relateIndex = createRelateOrLinkTag(objectIndex=objectIndex,relationshipIndex=manyToOneIndex,from=arguments.qRelationshipData.fkColumn_Name,to=arguments.qRelationshipData.pkColumn_Name,type="relate")>
		
			<!--- Find the foreign key field and add the parent and display attributes --->
			<cfset fieldIndex = getFieldPosition(objectIndex=objectIndex,name=arguments.qRelationshipData.fkColumn_Name)>
			
			<!--- Set up a pointer to the structure containing the attributes of the field tag --->
			<cfset stProperties = variables.xScaffoldingConfig.scaffolding.objects.object[objectIndex].XmlChildren[FieldIndex].XmlAttributes >	
			<!--- Add the parent and display attributes --->
			<cfset setAttributeValue(stProperties,"parent",arguments.qRelationshipData.pktable_name) >
			<cfset setAttributeValue(stProperties,"display",arguments.qRelationshipData.pkColumn_Name) >
		</cfloop>
		
	</cffunction>
	
	<!--- *** Get and set the current table *** --->
	<cffunction name="setSelectedTable" returntype="void" output="No" >
		<cfargument name="selectedTable" type="string" required="Yes" />
		<cfset variables.selectedTable = arguments.selectedTable>
	</cffunction>
	<cffunction name="getSelectedTable" returntype="string">
		<cfreturn variables.selectedTable>
	</cffunction>
	
	<cffunction name="getSelectedTableAlias" returntype="string" output="No" >
		<cfset var xTable = XmlSearch(variables.xScaffoldingConfig,"/scaffolding/objects/object[@name='#variables.selectedTable#']")>
		<cfif structKeyExists(xTable[1].xmlAttributes,"alias")>
			<cfreturn xTable[1].xmlAttributes["alias"]>
		<cfelse>
			<cfreturn xTable[1].xmlAttributes["name"]>
		</cfif>
	</cffunction>
	
	<!--- *** Get and set the list of tables to generate code for *** --->
	<cffunction name="setLTables" returntype="void" output="No" >
		<cfargument name="lTables" type="string" required="Yes" />
		<cfset variables.lTables = arguments.lTables>
	</cffunction>
	<cffunction name="getLTables" returntype="string">
		<cfreturn variables.lTables>
	</cffunction>
	
	<cffunction name="getLTableAliases" returntype="string" output="No" >
		<cfset var xTable = "">
		<cfset var lTableAliases = "">
		
		<cfloop list="#variables.lTables#" index="thisTable">
			<cfset xTable = XmlSearch(variables.xScaffoldingConfig,"/scaffolding/objects/object[@name='#thisTable#']")>
			<cfif structKeyExists(xTable[1].xmlAttributes,"alias")>
				<cfset lTableAliases = listAppend(lTableAliases,xTable[1].xmlAttributes["alias"])>
			<cfelse>
				<cfset lTableAliases = listAppend(lTableAliases,xTable[1].xmlAttributes["name"])>
			</cfif>
		</cfloop>
		<cfreturn lTableAliases>
	</cffunction>

<!--- 																									 --->
<!--- *** The following functions convert the XML data into array formats for easier code generation *** --->
<!--- 																									 --->
	
	<cffunction name="getTablesFromXML" returntype="array" output="No"  
				hint="I return an array containing the names of the tables from the XML Configuration.">
		<cfset var i = 0>
		<cfset var aTables = ArrayNew(1)>
		<cfset var xTables = XmlSearch(variables.xScaffoldingConfig,"/scaffolding/objects/object")>
		
		<cfloop index="i" from="1" to="#arrayLen(xTables)#">
			<cfset arrayAppend(aTables,xTables[i].XmlAttributes.name)>
		</cfloop>
		
		<cfreturn aTables>
	</cffunction>
	
	<cffunction name="getFieldsFromXML" returntype="array" output="No" 
				hint="I return an array containing the fields of the selected table from the XML Configuration.">
		<cfargument name="table" type="string" required="Yes" hint="I am the name of the table who's fields are to be output.">
		<cfset var i = 0>
		<cfset var aFields = ArrayNew(1)>
		<cfset var quotedTable = "'#arguments.table#'">
		<cfset var xFields = XmlSearch(variables.xScaffoldingConfig,"/scaffolding/objects/object[@name=#quotedTable#]/field")>
		
		<cfloop index="i" from="1" to="#arrayLen(xFields)#">
			<cfset arrayAppend(aFields,xFields[i].XmlAttributes)>
			<cfset aFields[i]["table"] = arguments.table>
			<!--- Set default values on missing entries in the XML --->
			<cfif NOT structKeyExists(aFields[i],"alias")>
				<cfset aFields[i]["alias"] = aFields[i]["name"]>
			</cfif>
			<cfif NOT structKeyExists(aFields[i],"label")>
				<cfset aFields[i]["label"] = cleanLabelText(aFields[i]["alias"])>
			</cfif>
			<cfif NOT structKeyExists(aFields[i],"showOnList")>
				<cfset aFields[i]["showOnList"] = "true">
			</cfif>
			<cfif NOT structKeyExists(aFields[i],"showOnForm")>
				<cfset aFields[i]["showOnForm"] = "true">
			</cfif>
			<cfif NOT structKeyExists(aFields[i],"type")>
				<cfset aFields[i]["type"] = "string">
			</cfif>
			<cfif NOT structKeyExists(aFields[i],"formType")>
				<cfif structKeyExists(aFields[i],"parent")>
					<cfset aFields[i]["formType"] = "Dropdown">
				<cfelseif aFields[i].type IS "date">
					<cfset aFields[i]["formType"] = "Calendar">
				<cfelseif aFields[i].type IS "boolean">
					<cfset aFields[i]["formType"] = "Checkbox">
				<cfelseif structKeyExists(aFields[i],"maxlength") AND aFields[i]["maxlength"] GT 200>
					<cfset aFields[i]["formType"] = "TextArea">
				<cfelse>
					<cfset aFields[i]["formType"] = "Text">
				</cfif>
			</cfif>
			
			<cfif NOT structKeyExists(aFields[i],"format")>
				<cfif aFields[i].type IS "date">
					<cfset aFields[i]["format"] = "Date(dd/mmm/yyyy)">
				<cfelseif aFields[i].type IS "boolean">
					<cfset aFields[i]["format"] = "YesNo">
				<cfelseif aFields[i].type IS "money">
					<cfset aFields[i]["format"] = "Currency">
				<cfelseif aFields[i].type IS "integer">
					<cfset aFields[i]["format"] = "Number(0)">
				<cfelseif aFields[i].type IS "float">
					<cfset aFields[i]["format"] = "Number(2)">
				<cfelse>
					<cfset aFields[i]["format"] = "Trim">
				</cfif>
			</cfif>
			
			<cfif NOT structKeyExists(aFields[i],"size")>
				<cfif aFields[i].formType IS "Dropdown">
					<cfset aFields[i]["size"] = "1">
				<cfelseif aFields[i].formType IS "TextArea">
					<cfset aFields[i]["size"] = "30x4">
				<cfelseif aFields[i].formType IS "Text">
					<cfif aFields[i].type IS "integer" OR aFields[i].type IS "float">
						<cfset aFields[i]["size"] = "15">
					<cfelse>
						<cfset aFields[i]["size"] = "30">
					</cfif>
				<cfelse>
					<cfset aFields[i]["size"] = "0">
				</cfif>
			</cfif>
		</cfloop>
		
		<cfreturn aFields>
	</cffunction>
	
	<cffunction name="getFieldListFromXML" returntype="string" output="No" 
				hint="I return a list of field aliases for a table." >
		<cfargument name="table" type="string" required="Yes" hint="I am the name of the table who's fields are to be output.">
		<cfset var i = 0>
		<cfset var aFields = getFieldsFromXML(arguments.table)>
		<cfset var lFields = "">
		
		<cfloop from="1" to="#arrayLen(aFields)#" index="i">
			<cfif structKeyExists(aFields[i],"alias")>
				<cfset lFields = ListAppend(lFields,aFields[i].alias)>
			<cfelse>
				<cfset lFields = ListAppend(lFields,aFields[i].name)>
			</cfif>
		</cfloop>
		
		<cfreturn lFields>
	</cffunction>
	
	<cffunction name="getPKListFromXML" returntype="string" output="No" 
				hint="I return a list of prinary key field aliases for a table." >
		<cfargument name="table" type="string" required="Yes" hint="I am the name of the table who's PK fields are to be output.">
		<cfset var i = 0>
		<cfset var aFields = getFieldsFromXML(arguments.table)>
		<cfset var aPKFields = ArrayNew(1)>
		<cfset var len = arrayLen(aFields)>
		<!--- Loop over the array of fields and find the Primary Keys --->
		<cfloop from="1" to="#len#" index="i">
			<cfif structKeyExists(aFields[i],"primaryKeySeq") AND aFields[i].primaryKeySeq GT 0>
				<cfset aPKFields[aFields[i].primaryKeySeq] = aFields[i].alias>
			</cfif>
		</cfloop>
		
		<cfreturn ArrayToList(aPKFields)>
	</cffunction>
	
	<cffunction name="getJoinedFieldListFromXML" returntype="string" output="No" 
				hint="I return a list of field aliases for the fields of joined tables to a table.">
		<cfargument name="table" type="string" required="Yes" hint="I am the name of the table who's fields are to be output.">
		<cfset var i = 0>
		<cfset var aFields = getFieldsFromXML(arguments.table)>
		<cfset var lFields = "">
		
		<cfloop from="1" to="#arrayLen(aFields)#" index="i">
			<cfif structKeyExists(aFields[i],"display")>
				<cfset lFields = ListAppend(lFields,aFields[i].display)>
			</cfif>
		</cfloop>
		
		<cfreturn lFields>
	</cffunction>
	
	<cffunction name="getJoinedFieldsFromXML" returntype="array" output="No" 
				hint="I return a array of fields from the joined tables to a table.">
		<cfargument name="table" type="string" required="Yes" hint="I am the name of the table who's joined fields are to be output.">
		<cfset var i = 0>
		<cfset var aFields = getFieldsFromXML(arguments.table)>
		<cfset var aJoinedFields = arrayNew(1)>
		
		<cfloop from="1" to="#arrayLen(aFields)#" index="i">
			<cfif structKeyExists(aFields[i],"parent")>
				<cfset aJoinedFields = ArrayConcat(aJoinedFields,getFieldsFromXML(aFields[i].parent))>
			</cfif>
		</cfloop>
		
		<cfreturn aJoinedFields>
	</cffunction>
	
	<cffunction name="getRelationshipsFromXML" returntype="array" output="No" 
				hint="I return a array containing the tables related to the selected table from the XML Configuration.">
		<cfargument name="table" required="Yes" hint="I am the name of the table who's realtionships are to be output.">
		<cfargument name="type" required="Yes" hint="I am the type of relationship required">
		<cfset var i = 0>
		<cfset var j = 0>
		<cfset var aRelationships = ArrayNew(1)>
		<cfset var quotedTable = "'#arguments.table#'">
		<cfset var stData = 0>
		<cfset var xRelationships = XmlSearch(variables.xScaffoldingConfig,"/scaffolding/objects/object[@name=#quotedTable#]/#arguments.type#")>
		
		<cfloop index="i" from="1" to="#arrayLen(xRelationships)#">
			<cfset stData = structNew()>
			<cfset stData["Name"] = xRelationships[i].XmlAttributes.name>
			<cfif structKeyExists(xRelationships[i].XmlAttributes,"alias")>
				<cfset stData["Alias"] = xRelationships[i].XmlAttributes.alias>
			<cfelse>
				<cfset stData["Alias"] = xRelationships[i].XmlAttributes.name>
			</cfif>
			<cfset stData["Links"] = arrayNew(1)>
			<cfloop index="j" from="1" to="#arrayLen(xRelationships[i].XmlChildren)#">
				<cfset stData.Links[j] = xRelationships[i].XmlChildren[j].XmlAttributes>
				<cfset stData.Links[j]["type"] = xRelationships[i].XmlChildren[j].XmlName>
			</cfloop>
			
			<cfset arrayAppend(aRelationships,stData)>
		</cfloop>
		
		<cfreturn aRelationships>
	</cffunction>
	
	
</cfcomponent>