Error On Second Iteration: Application-defined or object-defined error - vba

This year I inherited support of about a dozen accdb applications in Office 2010 Win 7 that often manipulate external excel files.
I keep getting the same error scenario. It is in my vba for excel commands,
but only AFTER the first iteration of a loop. It always works fine the first time through. Seems to have something to do with how I am identifying the objects. I've read multiple articles on best practices for working with the objects and the specific error but nothing has translated into a solution. Can someone ELI5 what I am doing wrong?
In the example below it is throwing the error early in the second iteration at the Range("A1").Select command.
Code:
Sub runCleanAndImportUnpre()
Dim strFolder As String
Dim strTableDest As String
strTableDest = "Unpresented_EOD_Import"
strFolder = "C:\Users\lclambe\Projects\Inputs\test2"
Call CleanAndImportUnpresentedInAGivenFolder(strTableDest, strFolder)
End Sub
Function CleanAndImportUnpresentedInAGivenFolder(strTable As String, strFolder As String)
' Function that opens files in a folder, cleans them up and saves them.
Dim myfile
Dim mypath
Dim strPathFileName As String
Dim i As Integer
'Call ClearData(strTable)
'if it needs a backslash on the end, add one
If Right(strFolder, 1) <> "\" Then
strFolder = strFolder & "\"
End If
mypath = strFolder
ChDir (strFolder)
myfile = Dir(mypath)
ChDir (mypath)
myfile = Dir("")
i = 1
Do While myfile <> ""
'Format the excel report
strPathFileName = mypath & myfile
'use for unpresented
Call formatExcelUnPresentedForImport(strPathFileName)
i = i + 1
myfile = Dir()
Loop
End Function
Function formatExcelUnPresentedForImport(filePath As String)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Note:
' Called from CleanAndImportUnpresentedInAGivenFolder when
' importing Unpresented reports
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
On Error GoTo formatExcelUnPresentedForImport_Error
Dim strFilePath As String
Dim strReportType As String
Dim i As Integer
Dim iTotal_Row
Dim Lastrow As Long
Dim iCol As Integer
Dim appExcel As excel.Application
Dim wkb As excel.Workbook
Dim sht As Worksheet
Dim rng As Range
strReportType = reportType
strFilePath = filePath
Set appExcel = New excel.Application
appExcel.Visible = False
'Define the worksheet
Set wkb = appExcel.Workbooks.Open(strFilePath, ReadOnly:=False)
'Turn off error msg: "minor loss of fidelity" if you are sure no data will be lost
wkb.CheckCompatibility = False
'Expand Column to avoid scientific notation
appExcel.Columns("A:A").EntireColumn.AutoFit
'Find last row
'FAILS HERE ON SECOND ITERATION OF LOOP:
Range("A1").Select
ActiveCell("A1").Select
Selection.End(xlDown).Select
'Delete the last 3 rows of totals
ActiveCell.offset(-2, 0).Select
Selection.EntireRow.Delete
Selection.EntireRow.Delete
Selection.EntireRow.Delete
'Add a TRIM of Cash Amount Field2 at column L
Range("L2").Select
ActiveCell.FormulaR1C1 = "=TRIM(RC[-9])"
Range("L2").Select
'Copy it to rest of cells to bottom
Selection.Copy
Lastrow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
Selection.AutoFill Destination:=Range("L2:L" & Lastrow), Type:=xlFillDefault
Range("L2:L" & Lastrow).Select
'Delete original unformatted unpresented
Selection.Copy
ActiveSheet.Paste
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
'Delete all the rows except Unpresented
Range("B:B,C:C,D:D,E:E,F:F,G:G,H:H,I:I,J:J,K:K").Select
Range("K1").Activate
Selection.Delete Shift:=xlToLeft
'Add a Header
Range("B1").Select
ActiveCell.FormulaR1C1 = "Unpresented"
wkb.Save
wkb.Close
appExcel.Quit
Set wkb = Nothing
Set appExcel = Nothing
On Error GoTo 0
Exit Function
formatExcelUnPresentedForImport_Error:
Set wkb = Nothing
Set appExcel = Nothing
strMessage = "Error " & err.Number & " (" & err.Description & ") in procedure formatExcelUnPresentedForImport of Module modExternalExcelClean."
strMessage = strMessage & " Application will stop processing now." & vbNewLine
strMessage = strMessage & "Please note or copy this error message and contact application developer for assistance."
MsgBox strMessage, vbCritical + vbOKOnly, "Error"
End
End Function

Just guessing that you are not iterating through an Excel file the second time, thus it throws an error. To debug it in ELI5 style, change your code like this:
Do While myfile <> ""
MsgBox myFile
'Format the excel report
strPathFileName = mypath & myfile
'use for unpresented
Call formatExcelUnPresentedForImport(strPathFileName)
i = i + 1
myfile = Dir()
Loop
and pay attention to the MsgBox every time. Is it showing what you think it should be showing?

Related

VBA Export Excel to CSV with Range

I used the code that I found here.
After some changes this is the code I have now:
Option Explicit
Sub ExportAsCSV()
Dim MyFileName As String
Dim Item As String
Dim Path As String
Dim CurrentWB As Workbook, TempWB As Workbook
Path = "F:\Excels\csv export\"
Set CurrentWB = ActiveWorkbook
ActiveWorkbook.Worksheets("Nieuw Artikelnummer").UsedRange.Copy
Item = Range("D2")
Set CurrentWB = ActiveWorkbook
ActiveWorkbook.Worksheets("csv").UsedRange.Copy
Set TempWB = Application.Workbooks.Add(1)
With TempWB.Sheets(1).Range("A1")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
End With
MyFileName = Path & "\" & Item & ".csv"
Application.DisplayAlerts = False
TempWB.SaveAs filename:=MyFileName, FileFormat:=xlCSV, CreateBackup:=False, Local:=True
TempWB.Close SaveChanges:=False
Application.DisplayAlerts = True
MsgBox ".csv file has been created: " _
& vbCrLf _
& MyFileName
End Sub
The problem I have is that it uses the UsedRange, but I would like to select the Range that is copied into the new .csv file.
What can I do to select the Range to copy into the new file instead of the UsedRange?
This will open an input box on the article number sheet that allows you to hand select or type in a range:
Sub ExportAsCSV()
Dim MyFileName As String
Dim Item As String
Dim Path As String
Dim CurrentWB As Workbook, TempWB As Workbook
Dim myrangeNA As Range
Dim myRangeCSV As Range
Path = "F:\Excels\csv export\"
Set CurrentWB = ActiveWorkbook
ActiveWorkbook.Worksheets("Nieuw Artikelnummer").Activate
Set myrangeNA = Application.InputBox(prompt:="Select a range to copy", Type:=8)
Item = Range("D2")
Set TempWB = Application.Workbooks.Add(1)
myrangeNA.Copy Destination:=TempWB.Worksheets("Sheet1").Range("A1")
MyFileName = Path & "\" & Item & ".csv"
Application.DisplayAlerts = False
TempWB.SaveAs Filename:=MyFileName, FileFormat:=xlCSV, CreateBackup:=False, Local:=True
TempWB.Close SaveChanges:=False
Application.DisplayAlerts = True
MsgBox ".csv file has been created: " _
& vbCrLf _
& MyFileName
End Sub
If you don't want to select it, change the myrangeNA to whatever range you want, like range("A5:C20") and it should work.
For situations like this, I prefer to isolate the actions to a standalone Sub or Function that I can call with parameters. In this way I can reuse it as needed, either in this project or another one.
So I've separated the actions of copying the selected data range and pasting to a temporary workbook, then saving to a CSV file in it's own Function. The action returns a True/False result as a check for success.
Option Explicit
Sub test()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
Dim destCSVfile As String
destCSVfile = "C:\Temp\" & ws.Range("D2")
If ExportAsCSV(Selection, destCSVfile) Then
MsgBox ".csv file has been created: " _
& vbCrLf _
& destCSVfile
Else
MsgBox ".csv file NOT created"
End If
End Sub
Private Function ExportAsCSV(ByRef dataArea As Range, _
ByVal myFileName As String) As Boolean
'--- make sure we have a range to export...
ExportAsCSV = False
If dataArea Is Nothing Then
Exit Function
End If
dataArea.Copy
'--- create a temporary workbook that will be saved as a CSV format
Dim tempWB As Workbook
Set tempWB = Application.Workbooks.Add(1)
With tempWB.Sheets(1).Range("A1")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
End With
'--- suppress alerts to convert the temp book to CSV
Application.DisplayAlerts = False
tempWB.SaveAs filename:=myFileName, FileFormat:=xlCSV, _
CreateBackup:=False, Local:=True
tempWB.Close SaveChanges:=False
Application.DisplayAlerts = True
ExportAsCSV = True
End Function
Your other two questions in the comment above mention pasting transposed values, which you would do by changing the line myrangeNA.Copy Destination:=TempWB.Worksheets("Sheet1").Range("A1") to
myrangeNA.Copy
TempWB.Worksheets("Sheet1").Range("A1").PasteSpecial _
Paste:=xlPasteValues, Transpose:=True
This site is a great reference source for all the various objects and methods and properties in the Office VBA collection: https://learn.microsoft.com/en-us/office/vba/api/overview/excel/object-model
(or https://learn.microsoft.com/de-de/office/vba/api/overview/excel/object-model if you prefer to have about five words translated to German)

How to not allow export as .CSV if any null values exist

I have a tab in excel that has about 50 columns. I export this tab as a .CSV file and upload it into a database. I am currently using this VBA code to export the .CSV file:
Sub ExportAsCSV()
Dim MyFileName As String
Dim CurrentWB As Workbook, TempWB As Workbook
Set CurrentWB = ActiveWorkbook
ActiveWorkbook.ActiveSheet.UsedRange.Copy
Set TempWB = Application.Workbooks.Add(1)
With TempWB.Sheets(1).Range("A1")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
End With
'Dim Change below to "- 4" to become compatible with .xls files
MyFileName = CurrentWB.Path & "\" & Left(CurrentWB.Name, Len(CurrentWB.Name) - 5) & ".csv"
Application.DisplayAlerts = False
TempWB.SaveAs Filename:=MyFileName, FileFormat:=xlCSV, CreateBackup:=False, Local:=True
TempWB.Close SaveChanges:=False
Application.DisplayAlerts = True
End Sub
There are certain columns in this export tab that will have "null" (actual word null) if the connected cells in other sheets are not filled in. How can I add to this existing VBA code to not allow an export if there are any null (the word, not blanks) values? Also how can a box pop up telling you that it wont export due to nulls?
I fixed your code's structure and added a test at the start which checks to make sure you have no "null" values anywhere on your ActiveSheet - if you do, it will throw a pop-up then exit the macro.
Sub ExportAsCSV()
If Application.WorksheetFunction.CountIf(ActiveSheet.UsedRange, "null") > 0 Then
MsgBox "Null values exist in the range - exiting sub.", vbExclamation
Exit Sub
End If
Dim MyFileName As String
Dim CurrentWB As Workbook, TempWB As Workbook
Set CurrentWB = ActiveWorkbook
ActiveWorkbook.ActiveSheet.UsedRange.Copy
Set TempWB = Application.Workbooks.Add(1)
With TempWB.Sheets(1).Range("A1")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
End With
'Dim Change below to "- 4" to become compatible with .xls files
MyFileName = CurrentWB.Path & "\" & Left(CurrentWB.Name, Len(CurrentWB.Name) - 5) & ".csv"
Application.DisplayAlerts = False
TempWB.SaveAs Filename:=MyFileName, FileFormat:=xlCSV, CreateBackup:=False, Local:=True
TempWB.Close SaveChanges:=False
Application.DisplayAlerts = True
End Sub
It's a lot more elaborate, but I think it's the right way to do it. Plus, it activates the first "null" cell for the end user to look at.
Add the following lines to the top of your code:
Sub ExportAsCSV()
Dim NullAddress As String
NullAddress = FindNull(ActiveSheet.UsedRange)
If NullAddress <> vbNullString Then
ActiveSheet.Range(NullAddress).Activate
MsgBox "Cannot Export due to ""null"" value in cell"
Exit Sub
End If
'
'
'
End Sub
which replies on the test function to do the heavy lifting:
Function FindNull(Target As Excel.Range) As String
Const NullValue As String = "null"
Dim vData 'As Variant
Dim Row As Long, Col As Long
If Not Target Is Nothing Then
vData = Target
If IsArray(vData) Then
For Row = 1 To Target.Rows.Count
For Col = 1 To Target.Columns.Count
If vData(Row, Col) = NullValue Then
' Return the Address of the first Null value found & Exit
FindNull = Target.Parent.Cells(Target.Cells(1).Row + Row - 1, Target.Cells(1).Column + Col - 1).Address(RowAbsolute:=False, ColumnAbsolute:=False)
Exit Function
End If
Next
Next
Else
If vData = NullValue Then FindNull = Target.Address
End If
End If
End Function

CRITICAL_ERROR 65535

I have a simple VBA routine I have used before but seem to keep returning Critical_Error 65535 ..this is my routine..it fails at the save part post generating the values only excel file ..any pointers please?
Sub SaveFile()
'Recalc Sheets prior to saving down
a = MsgBox("Do you want to Save down todays Consultant Performance Report ?", vbOKCancel)
If a = 2 Then Exit Sub
Dim SaveSheets As Variant
Dim strFilename As String
Dim sheetListRange As Range
Dim sheetName As Variant
Dim wksheet As Variant
Dim wkbSrc As Workbook
Dim wkbNew As Workbook
Dim wksNew As Worksheet
Dim wksSrc As Worksheet
Dim i As Integer
On Error GoTo ErrorHandler
strFilename = Worksheets("Control").Range("SavePath").Value & "DailyReport_" & Format$(Now(), "YYYYMMDD") & ".xls"
Set sheetListRange = Worksheets("Control").Range("SaveList")
Set wkbSrc = ActiveWorkbook
Set wkbNew = Workbooks.Add
i = 0
For Each sheetName In sheetListRange
If sheetName = "" Then GoTo NEXT_SHEET
For Each wksheet In wkbSrc.Sheets
If wksheet.Name = sheetName Then
i = i + 1
wksheet.Copy Before:=wkbNew.Sheets(i)
Set wksNew = ActiveSheet
With wksNew
.Cells.Select
.Cells.Copy
.Cells(1, 1).PasteSpecial Paste:=xlPasteValues
.Cells(1, 1).PasteSpecial Paste:=xlPasteFormats
End With
ActiveWindow.Zoom = 75
GoTo NEXT_SHEET
End If
Next wksheet
NEXT_SHEET:
Next sheetName
Application.DisplayAlerts = False
'dont need the default new sheets created by created a new workbook
wkbNew.Worksheets("Sheet1").Delete
wkbNew.Worksheets("Sheet2").Delete
wkbNew.Worksheets("Sheet3").Delete
ActiveWorkbook.SaveAs Filename:=strFilename, FileFormat:=xlWorkbookNormal
ActiveWorkbook.Close
Application.DisplayAlerts = True
Exit Sub
ErrorHandler:
'If there is an unknown runtime error give the user the error number and associated description
'(Description is already set if the erorr is G_LNG_CRITICAL_ERROR)
If Err.Number <> CRITICAL_ERROR Then Err.Description = "Run-time error " & Err.Number & ": " & Err.Description
Err.Description = "Error saving worksheet as file: " & Err.Description
'Raise the error up to the error handler above
Err.Raise Number:=CRITICAL_ERROR
End Sub

excel cell = filename of another workbook

I have a macro for updating a sheet from another workbook, how can I use that same file to update a cell with its filename without the .xlsx.
Can I use the vFile or wbCopyFrom Dim?
Sub UpdateTSOM()
Application.ScreenUpdating = False
Dim vFile As Variant
Dim wbCopyTo As Workbook
Dim wsCopyTo As Worksheet
Dim wbCopyFrom As Workbook
Dim wsCopyFrom As Worksheet
Set wbCopyTo = ActiveWorkbook
Set wsCopyTo = ActiveSheet
If MsgBox("Update Transmission Stock Status data?", vbYesNo) = vbYes Then
Worksheets("TSOM").Range("B2:N3000").ClearContents
Else: Exit Sub
End If
On Error GoTo whoa
'Open file with data to be copied
vFile = "C:\Users\taylorm1\Desktop\OUC\_Materials\Stock Status\Transmission Stock Status*.xlsx"
'vFile = "P:\ESO\1790-ORL\OUC\_Materials\Stock Status\Transmission Stock Status **-**-**.xlsx"
Set wbCopyFrom = Workbooks.Open(vFile)
Set wsCopyFrom = wbCopyFrom.Worksheets(1)
'Copy Range
wsCopyFrom.Range("A1:N3000").Copy
'wsCopyFrom.Range("A1:A" & LastRow).Copy
wsCopyTo.Range("B2").PasteSpecial Paste:=xlPasteValues, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
SendKeys "Y"
SendKeys ("{ESC}")
'Close file that was opened
wbCopyFrom.Close SaveChanges:=False
Application.Wait (Now + 0.000005)
Call NoSelect
Exit Sub
Application.ScreenUpdating = True
whoa:
vFile = Application.GetOpenFilename("Excel Files (*.xl*)," & _
"*.xl*", 1, "Select Excel File", "Open", False)
Set wbCopyFrom = Workbooks.Open(vFile)
Set wsCopyFrom = wbCopyFrom.Worksheets(1)
'Copy Range
wsCopyFrom.Range("A1:N3000").Copy
'wsCopyFrom.Range("A1:A" & LastRow).Copy
wsCopyTo.Range("B2").PasteSpecial Paste:=xlPasteValues, _
Operation:=xlNone, SkipBlanks:=False, Transpose:=False
SendKeys "Y"
SendKeys ("{ESC}")
'Close file that was opened
wbCopyFrom.Close SaveChanges:=False
Application.Wait (Now + 0.000005)
Call NoSelect
Exit Sub
'whoa: 'If filename changes then open folder
'Call Shell("explorer.exe" & " " & "P:\ESO\1790-ORL\OUC\_Materials\Stock Status", vbNormalFocus)
End Sub
Thanks
You can get the file's name without path and without extension like this:
Dim s As String
s = Mid(vFile, InStrRev(vFile, "\") + 1)
s = Left$(s, InStrRev(s, ".") - 1)
Or if you want to keep the full path but only remove the extension:
Dim s As String
s = Left(vFile, InStrRev(vFile, ".") - 1)
Then assign it to any cell: myCell.Value = s
Try this code.
Private Sub TestNettFileName()
Debug.Print NettFileName(ThisWorkbook.Name)
End Sub
Private Function NettFileName(Fn As String) As String
Dim Sp() As String
Sp = Split(ActiveWorkbook.Name, ".")
ReDim Preserve Sp(UBound(Sp) - 1)
NettFileName = Join(Sp, ".")
End Function
Use it in your project like,
With ActiveSheet
.Range("A3").Value = NettFileName(.Parent.Name)
End With

error handler works only once in vba

I am trying to pull data from several workbooks which have different sheet names. I have created an array which contains all the possible sheet names. When data workbook opens and sheet name is not found the error handler works for the first time when loop runs again and pull the next array element, error handler doesn't work. It gives "Subscript out of range" error. Can anyone please elaborate what am I missing here? What I want is in case consecutive sheet names are not available in data workbook, code should go into for loop again and search for next sheet name.
Public strFileName As String
Public currentWB As Workbook
Public dataWB As Workbook
Sub GetData()
Dim strListSheet As String
Dim i As Integer
Dim VendorValue As String
Dim SheetNames() As Variant
Dim a As String
strListSheet = "Master"
Sheets(strListSheet).Select
Range("First_file").Select
SheetNames = Range("Sheet_Names")
'this is the main loop, we will open the files one by one and copy their data into the masterdata sheet
Set currentWB = ActiveWorkbook
Do While ActiveCell.Value <> ""
strFileName = ActiveCell.Offset(0, 1) & ActiveCell.Value
VendorValue = ActiveCell.Offset(0, 2)
Application.Workbooks.Open strFileName, UpdateLinks:=False, ReadOnly:=True
Set dataWB = ActiveWorkbook
For i = LBound(SheetNames, 1) To UBound(SheetNames, 1)
a = SheetNames(i, 1)
b = SheetNames(i, 2)
dataWB.Activate
On Error GoTo Handler:
ActiveWorkbook.Sheets(a).Select
Range("H5:H120,I5:I120,M5:M120,P5:P120,U5:X120").Select
Selection.Copy
currentWB.Activate
Sheets(VendorValue).Select
Range(b).Select
Selection.PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
Application.CutCopyMode = False
Handler:
Next
dataWB.Close False
Sheets(strListSheet).Select
ActiveCell.Offset(1, 0).Select
Loop
Exit Sub
End Sub
You have to exit the error handler in order to reuse it. That is you need a Resume clause at the end of your error handler.
Check this site for more details.
I have moved the handler at the end of the sub and added a Resume.
Public strFileName As String
Public currentWB As Workbook
Public dataWB As Workbook
Sub GetData()
Dim strListSheet As String
Dim i As Integer
Dim VendorValue As String
Dim SheetNames() As Variant
Dim a As String
strListSheet = "Master"
Sheets(strListSheet).Select
Range("First_file").Select
SheetNames = Range("Sheet_Names")
'this is the main loop, we will open the files one by one and copy their data into the masterdata sheet
Set currentWB = ActiveWorkbook
Do While ActiveCell.Value <> ""
strFileName = ActiveCell.Offset(0, 1) & ActiveCell.Value
VendorValue = ActiveCell.Offset(0, 2)
Application.Workbooks.Open strFileName, UpdateLinks:=False, ReadOnly:=True
Set dataWB = ActiveWorkbook
For i = LBound(SheetNames, 1) To UBound(SheetNames, 1)
a = SheetNames(i, 1)
b = SheetNames(i, 2)
dataWB.Activate
On Error GoTo Handler:
ActiveWorkbook.Sheets(a).Select
Range("H5:H120,I5:I120,M5:M120,P5:P120,U5:X120").Select
Selection.Copy
currentWB.Activate
Sheets(VendorValue).Select
Range(b).Select
Selection.PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
Application.CutCopyMode = False
Handler2:
Next
dataWB.Close False
Sheets(strListSheet).Select
ActiveCell.Offset(1, 0).Select
Loop
Exit Sub
Handler:
Resume Handler2
End Sub
I'd change approach like follows:
Dim mySht as Worksheet
a = SheetNames(i, 1)
Set mySht = GetSheet(dataWB, a)
If Not mySht Is Nothing Then
b = SheetNames(i, 2)
With mySht
.Range("H5:H120,I5:I120,M5:M120,P5:P120,U5:X120").Copy
currentWB.Sheets(VendorValue).Range(b).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
Application.CutCopyMode = False
End With
End If
Where I only showed the part that goes from a and b settings (included) to the Handler label (included, i.e. it has to disappear).
And you have to put this code in any module (also at the end of your Sub will do):
Function GetSheet(wb as Workbook, shtName as String)
On Error Resume Next
Set GetSheet = wb.Worksheet(shtName)
End Function
Finally the rest of your code can avoid a lot of Activate/Active/Select/Selection stuff in a similar manner
If all your files are in the same path, I think it's easier to use this:
Sub openOtherWorkbooks()
Dim folderPath As String, path As String
folderPath = "C:\Path\to\your\files"
path = folderPath & "\*.xlsm" 'xlsm as an example - could be xls* as well
Do While Filename <> ""
Filename = Dir()
If Filename <> ThisWorkbook.Name And Filename <> "" Then
Workbooks.Open folderPath & "\" & Filename
For i = 1 To Workbooks(Filename).Sheets.count
' do everything with every sheet of this file
Next i
Workbooks(Filename).Close False
End If
Filename = Dir(path)
Loop
End Sub
It's just opening every file, counting the sheets (beginning with 1) of the opened file and then there should be your code.
It's not exactly an answer to your On-Error-GoTo-thing with your handler.