<% '======================================================================================================================= ' IMPORT FILE HELPERS ' Reads CSV / XLSX / XLS uploads into a header array + rows dictionary, and normalizes values for the businesses table. ' ' ImportFile_Read(path, maxRows) returns a Scripting.Dictionary: ' "headers" -> Array of column names ' "rows" -> Scripting.Dictionary (0-based index -> Array of string values), at most maxRows rows (0 = all) '======================================================================================================================= Function ImportFile_UploadDir() Dim fso : Set fso = CreateObject("Scripting.FileSystemObject") Dim dir : dir = fso.GetAbsolutePathName(Server.MapPath("/") & "\..\uploads") If Not fso.FolderExists(dir) Then fso.CreateFolder dir ImportFile_UploadDir = dir End Function ' Returns the full path of the stored upload for a token, or "" when the token is malformed or the file is missing. Function ImportFile_PathForToken(token) Dim re : Set re = New RegExp re.Pattern = "^[0-9A-Fa-f]{32}$" ImportFile_PathForToken = "" If Not re.Test(token) Then Exit Function Dim fso, ext, p Set fso = CreateObject("Scripting.FileSystemObject") For Each ext In Array("csv", "xlsx", "xls") p = ImportFile_UploadDir() & "\" & token & "." & ext If fso.FileExists(p) Then ImportFile_PathForToken = p Exit Function End If Next End Function Function ImportFile_NewToken() Dim tl : Set tl = CreateObject("Scriptlet.TypeLib") ImportFile_NewToken = Replace(Replace(Replace(Left(CStr(tl.Guid), 38), "{", ""), "}", ""), "-", "") Set tl = Nothing End Function Function ImportFile_Read(path, maxRows) Dim ext : ext = LCase(Mid(path, InStrRev(path, ".") + 1)) If ext = "csv" Then Set ImportFile_Read = ImportFile_ReadCsv(path, maxRows) Else Set ImportFile_Read = ImportFile_ReadExcel(path, ext, maxRows) End If End Function '--------------------------------------------------------------------------------------------------------------------- ' CSV '--------------------------------------------------------------------------------------------------------------------- Function ImportFile_ReadCsv(path, maxRows) Dim stm, text Set stm = Server.CreateObject("ADODB.Stream") stm.Type = 2 stm.Charset = "utf-8" stm.Open stm.LoadFromFile path text = stm.ReadText stm.Close ' Not valid UTF-8 (replacement character present): re-read as Windows-1252, which is what Excel "CSV" exports use. If InStr(text, ChrW(65533)) > 0 Then stm.Charset = "windows-1252" stm.Open stm.LoadFromFile path text = stm.ReadText stm.Close End If Set stm = Nothing text = Replace(text, vbCrLf, vbLf) text = Replace(text, vbCr, vbLf) ' Pick the delimiter that appears most in the first line. Dim firstLine, delim, best, cand, c firstLine = Left(text, InStr(text & vbLf, vbLf) - 1) delim = "," best = 0 For Each cand In Array(",", ";", vbTab, "|") c = Len(firstLine) - Len(Replace(firstLine, cand, "")) If c > best Then best = c : delim = cand Next Dim lines, i, line, pending, all, rec lines = Split(text, vbLf) Set all = CreateObject("Scripting.Dictionary") pending = "" For i = 0 To UBound(lines) line = lines(i) If Len(pending) > 0 Then line = pending & vbLf & line pending = "" End If ' An odd number of quotes means a quoted field continues onto the next physical line. If ((Len(line) - Len(Replace(line, """", ""))) Mod 2) = 1 And i < UBound(lines) Then pending = line ElseIf Len(Trim(line)) > 0 Then all.Add all.Count, ImportFile_SplitCsvLine(line, delim) If maxRows > 0 And all.Count > maxRows Then Exit For End If Next Set ImportFile_ReadCsv = ImportFile_Pack(all) End Function Function ImportFile_SplitCsvLine(line, delim) If InStr(line, """") = 0 Then ImportFile_SplitCsvLine = Split(line, delim) Exit Function End If Dim fields(), n, i, ch, cur, inQ ReDim fields(Len(line)) n = 0 : cur = "" : inQ = False : i = 1 Do While i <= Len(line) ch = Mid(line, i, 1) If inQ Then If ch = """" Then If Mid(line, i + 1, 1) = """" Then cur = cur & """" i = i + 1 Else inQ = False End If Else cur = cur & ch End If ElseIf ch = """" Then inQ = True ElseIf ch = delim Then fields(n) = cur : n = n + 1 : cur = "" Else cur = cur & ch End If i = i + 1 Loop fields(n) = cur ReDim Preserve fields(n) ImportFile_SplitCsvLine = fields End Function '--------------------------------------------------------------------------------------------------------------------- ' Excel (via ACE OLEDB; 32-bit provider, so this only works in a 32-bit app pool / IIS Express x86) '--------------------------------------------------------------------------------------------------------------------- Function ImportFile_ReadExcel(path, ext, maxRows) Dim conn, rs, schema, sheet, props, i, n, all, rec, v, ncols If ext = "xls" Then props = "Excel 8.0" Else props = "Excel 12.0 Xml" Set conn = Server.CreateObject("ADODB.Connection") conn.Open "Provider=Microsoft.ACE.OLEDB.16.0;Data Source=" & path & ";Extended Properties=""" & props & ";HDR=YES;IMEX=1"";" Set schema = conn.OpenSchema(20) ' adSchemaTables sheet = "" Do While Not schema.EOF If schema("TABLE_TYPE") = "TABLE" Or schema("TABLE_TYPE") = "SYSTEM TABLE" Then If InStr(schema("TABLE_NAME"), "_xlnm") = 0 And InStr(schema("TABLE_NAME"), "FilterDatabase") = 0 Then sheet = schema("TABLE_NAME") Exit Do End If End If schema.MoveNext Loop schema.Close Set all = CreateObject("Scripting.Dictionary") If Len(sheet) > 0 Then Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "SELECT * FROM [" & sheet & "]", conn, 0, 1 ncols = rs.Fields.Count ReDim rec(ncols - 1) For i = 0 To ncols - 1 rec(i) = rs.Fields(i).Name Next all.Add 0, rec Do While Not rs.EOF ReDim rec(ncols - 1) For i = 0 To ncols - 1 v = rs.Fields(i).Value If IsNull(v) Then rec(i) = "" Else rec(i) = Trim(CStr(v)) Next all.Add all.Count, rec If maxRows > 0 And all.Count > maxRows Then Exit Do rs.MoveNext Loop rs.Close End If conn.Close Set ImportFile_ReadExcel = ImportFile_Pack(all) End Function ' Splits the raw row dictionary (row 0 = header) into headers + data rows. Function ImportFile_Pack(all) Dim result, rows, i Set result = CreateObject("Scripting.Dictionary") Set rows = CreateObject("Scripting.Dictionary") If all.Count = 0 Then result.Add "headers", Array() Else Dim hdr : hdr = all(0) For i = 0 To UBound(hdr) hdr(i) = Trim(Replace(hdr(i), ChrW(65279), "")) Next result.Add "headers", hdr For i = 1 To all.Count - 1 rows.Add rows.Count, all(i) Next End If result.Add "rows", rows Set ImportFile_Pack = result End Function Function ImportFile_Cell(rec, idx) ImportFile_Cell = "" If idx >= 0 Then If idx <= UBound(rec) Then ImportFile_Cell = Trim(rec(idx)) End If End Function '======================================================================================================================= ' FIELD DEFINITIONS AND NORMALIZATION '======================================================================================================================= ' Importable fields: Array(key, label) Function ImportFile_Fields() ImportFile_Fields = Array( _ Array("name", "Business name"), _ Array("contact_name", "Contact name"), _ Array("industry", "Industry / type of business"), _ Array("phone", "Phone"), _ Array("email", "Email"), _ Array("website", "Website"), _ Array("address1", "Address"), _ Array("city", "City"), _ Array("state", "State"), _ Array("zip", "Zip"), _ Array("notes", "Notes") _ ) End Function ' Guesses which field a column header represents ("" = ignore). Function ImportFile_GuessField(header) Dim h : h = LCase(Trim(header)) Dim rules, r rules = Array( _ Array("contact_name", "contact|owner|person|first.?name|full.?name"), _ Array("email", "e-?mail"), _ Array("phone", "phone|tel|mobile|cell"), _ Array("website", "web|url|site"), _ Array("industry", "industry|category|categories|business.?type|type|niche|sic|naics"), _ Array("zip", "zip|postal|post.?code"), _ Array("state", "^st$|state|province|region"), _ Array("city", "city|town"), _ Array("address1", "address|street"), _ Array("notes", "note|comment|description"), _ Array("name", "business|company|org|^name$|name") _ ) Dim re : Set re = New RegExp re.IgnoreCase = True For Each r In rules re.Pattern = r(1) If re.Test(h) Then ImportFile_GuessField = r(0) Exit Function End If Next ImportFile_GuessField = "" End Function ' Phone as digits only (leading US country code dropped); "" when fewer than 7 digits. Function ImportFile_PhoneDigits(phone) Dim re, d Set re = New RegExp re.Global = True re.Pattern = "[^0-9]" d = re.Replace(phone, "") If Len(d) = 11 And Left(d, 1) = "1" Then d = Mid(d, 2) If Len(d) < 7 Then d = "" ImportFile_PhoneDigits = Left(d, 20) End Function ' Digits of a search term, without the 7-digit minimum used for stored phone numbers. Function ImportFile_PhoneDigitsLoose(s) Dim re : Set re = New RegExp re.Global = True re.Pattern = "[^0-9]" ImportFile_PhoneDigitsLoose = re.Replace(s, "") End Function ' Zip codes that lost their leading zero in Excel (4 digits) get it back. Function ImportFile_NormalizeZip(zip) Dim z : z = Trim(zip) If Len(z) > 2 Then If Right(z, 2) = ".0" Then If IsNumeric(Left(z, Len(z) - 2)) Then z = Left(z, Len(z) - 2) End If End If If Len(z) = 4 And IsNumeric(z) Then z = "0" & z ImportFile_NormalizeZip = z End Function %>