1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
' Исправленная версия Export_to_Excel_(BIFF).SBS
' Размещено в SPSSX-L, 15.01.2002, автор: K.Asselberghs

' Экспортирует Сводные таблицы из SPSS на один и тот же лист рабочей книги Excel.
' Таблицы разделяются пустыми строками.
' * Чтобы экспортировать и другие объекты, поправьте процедуру ExportItems().
' * ПРИМЕЧАНИЕ
'    Перед запуском скрипта, запустите Excel и выделите ячейку, с которой должна начаться вставка импортируемых таблиц.

'*****************************************************************************.

'ОПИСАНИЕ
'Этот скрипт экспортирует Сводные таблицы SPSS в Excel, используя BIFF (Binary Interchange File Format - двоичный формат файла для обмена).
'Конец ОПИСАНИЯ
'
'******************************************************
'При необходимости отредактируйте макрос Excel (см. ниже), который
'переформатирует таблицу после вставки в Excel
'******************************************************

Sub ExcelMacro()
  objExcelApp.Selection.AutoFormat Format:=&HFFFFEFC6, Number:=True, _
    Font:=True, Alignment:=True, Border:=True, Pattern:=True, Width:=True
    'Вы можете запросить в Excel выполнение этого макроса
End Sub
'
'******************************************************
'ДАЛЕЕ НИКАКИХ ИЗМЕНЕНИЙ НЕ ТРЕБУЕТСЯ
'******************************************************


'используется как заголовок в диалоговых окнах
Const SCRIPT_NAME As String = "Экспорт в рабочую книгу Excel"
'используются для подавления предупреждений и возврата прежних установок для предотвращения нежелательных диалоговых окон
Const ALERTS_PRESERVE As Boolean = False
Const ALERTS_RESTORE As Boolean = True

Option Explicit
'Вызов функции Windows API, даёт больший контроль, чем функция Wait из Sax Basic
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

'глобальные переменные, используются большинством процедур
Dim objExcelApp As Object
Dim objOutput As ISpssOutputDoc

'для уведомления пользователя о невозможности вставки объекта...
Dim s_intErrorCount As Integer


Sub Main
    Dim strFileName As String

    On Error Resume Next

    'Начиная с SPSS 8.0 мы можем вызывать этот скрипт из синтаксиса.
    'В таком случае, нам потребуется подавить предупреждения, которые будут приостанавливать исполнение программы.
    'Но в SPSS 7.5 это вызовет ошибку.  Посему вся обработка предупреждений встроена в процедуру Alerts.
    'Мы вызываем её здесь только для инициализации установок. Она должна быть вызвана снова для восстановления
    'установок перед окончанием работы скрипта.
    Alerts(ALERTS_PRESERVE)

    'Отменяем экспорт, если в окне результатов пусто.
    If objSpssApp.Documents.OutputDocCount > 0 Then
        Set objOutput = objSpssApp.GetDesignatedOutputDoc
    Else
        'процедура ErrorBox передаёт свои аргументы для отображения в MsgBox, но перед этим проверяет процедуру Alerts.
        ErrorBox "В SPSS отсутствуют результаты для экспорта. " & vbCrLf & _
            "Пожалуйста, выполните анализ и запустите скрипт снова.", vbExclamation, SCRIPT_NAME
        'Всегда восстанавливаем установки перед выходом!
        Alerts(ALERTS_RESTORE)
        End
    End If

    'Получим имя файла, в котором будут сохранены результаты.
    'С 8-й версии SPSS скрипт может вызываться из синтаксиса, которой передаст ему имя файла в качестве параметра.
    'Если нет, запросим имя файла у пользователя.
     '@strFileName = GetFileName()

    'Следующее условие может быть опущено,
    'тогда результаты могут быть экспортированы, но без сохранения файла.
 '@ If strFileName = "" Then
 '@     'Пользователь отменил выбор, либо указанный в синтаксисе при вызове файл не может быть перезаписан.
 '@     'Всегда восстанавливаем установки перед выходом!
 '@     Alerts(ALERTS_RESTORE)
 '@     End
 '@ End If

    'Запустим Excel и сохраним ссылку на это приложение в глобальной переменной objExcelApp.
    CreateExcel

    'Вот, наконец-то, место, где мы начинаем что-то делать по сути!
    ExportItems

    'Сохраняем файл. Следовало бы сделать это в виде подпрограммы, если бы это не ограничивалось одной строкой кода.
    '@ objExcelApp.ActiveWorkbook.SaveAs FileName:=strFileName

    'Скажем пользователю, были ли объекты, которые не удалось скопировать
    If s_intErrorCount > 0 Then
        '... но только если предупреждения не отключены
        ErrorBox "Некоторые объекты могли не вполне корректно скопироваться и вставиться в Excel." & vbCrLf & _
            "Проверьте выдачу SPSS и полученный документ Excel.", vbExclamation, SCRIPT_NAME
    End If
        MsgBox "Готово!"
    'В последний раз:
    'Всегда восстанавливаем установки перед выходом!
    Alerts(ALERTS_RESTORE)
End Sub


'Ищет объекты, подходящие для экспорта в Excel. Вставляет их в Excel и запускает форматирующий макрос.
Sub ExportItems()
    Dim objItems As ISpssItems
    Dim objItem As ISpssItem
    Dim i As Long
    Dim intFootnotes As Integer

    On Error Resume Next
    '@ objExcelApp.Workbooks.Add

    Set objItems = objOutput.Items
    For i = 0 To objItems.Count - 1
        Set objItem = objItems.GetItem(i)
        Debug.Print "Объект: " & i & ", тип: " & objItem.SPSSType & _
            ", видимый: " & objItem.Visible
        'С ОДНОЙ ИЗ СЛЕДУЮЩИХ ДВУХ СТРОК СЛЕДУЕТ СНЯТЬ КОММЕНТАРИЙ
       'If objItem.Visible And objItem.Selected Then   'копируем только выделенные объекты
        If objItem.Visible Then                         'копируем всё видимое содержимое
           'ВЫБИРАЕМ, ЧТО И КАК ДОЛЖНО БЫТЬ ЭКСПОРТИРОВАНО В EXCEL
            Select Case objItem.SPSSType
                Case SPSSPivot  ', SPSSWarning  ', SPSSNote     'пропустим Notes, если закомментировано
                    'PasteIntoExcel objItem, "Picture (Enhanced Metafile)"
                    'закомментируйте остаток этого варианта (Case), если вставляете объект как картинку
                   PasteIntoExcel objItem, "Biff"
                    'применяем макрос Excel для форматирования таблицы
                    'ExcelMacro
               'Case SPSSLog, SPSSText, SPSSTitle
                    'PasteIntoExcel objItem, "Text"
                Case SPSSChart, SPSSIGraph
                     'PasteIntoExcel objItem, "Picture (Enhanced Metafile)"
                Case Else
                    'ничего не делаем
            End Select
        End If
    Next
    Err.Clear
End Sub


'Непосредственно контролирует процесс копирования и вставки.
'Используем технику отката и повторных попыток для борьбы со скрытыми ошибками буфера обмена.
Sub PasteIntoExcel (objItem As ISpssItem, strFormat As String)
    Static intSheet As Integer

    On Error Resume Next

    Dim lngSleep As Long
    Dim nrows As Integer

    lngSleep = 100  '1/10-я секунды

    Clipboard ""    '.Clear (очистим буфер обмена)

    objOutput.ClearSelection
    objItem.Selected = True

    'Копируем объект. Следующий за этим цикл - только если возникли проблемы.
    Do
        Sleep lngSleep
        objOutput.Copy
        If Err Then
            'буфер обмена может быть недоступен в этот момент для вставки результата метода copy
            'попробуем подождать и попытатемся снова
            lngSleep = 2 * lngSleep
        End If
    Loop Until (Err = 0) Or (lngSleep > 2000)

    If Err Then 'что-то не так с копированием. Попробуем сказать об этом пользователю
        Clipboard ">>> Объект не может быть скопирован: Ошилка # " & Err & vbCrLf & Err.Description
        strFormat = "Text"
        s_intErrorCount = s_intErrorCount + 1
        Err.Clear
    End If

 '@ intSheet = intSheet + 1
 '@ If intSheet > objExcelApp.Sheets.Count Then
 '@     objExcelApp.Sheets.Add
 '@ Else
 '@     objExcelApp.Sheets("Blad" & Trim$(CStr$(intSheet))).Select
 '@ End If

    lngSleep = 100
    Do
        Sleep lngSleep

        objExcelApp.ActiveSheet.PasteSpecial Format:=strFormat, Link:=False, DisplayAsIcon:= False
                nrows = objExcelApp.Selection.Areas(1).Rows.Count + 1 'KA
        'MsgBox CStr(nrows)

        If Err Then
            Debug.Print "Paste Error: " & Err; Err.Description
            'буфер обмена в этот момент может быть недоступен для вставки
            'попробуем подождать и попытаемся снова
            lngSleep = 2 * lngSleep
        End If
    Loop Until (Err=0) Or (lngSleep > 2000)

    If Err Then
        s_intErrorCount = s_intErrorCount + 1
        Err.Clear
    End If

    objExcelApp.ActiveCell.Offset(nrows, 0).Activate  'KA

End Sub


Function GetFileName() As String
    Dim strFileName As String

    'Во-первых, проверим, не вызван ли скрипт из синтаксиса,
    'и не передано ли имя файла в качестве параметра.

    On Error Resume Next
    'следующая операция вызовет ошибку в SPSS 7.5
    strFileName = objSpssApp.ScriptParameter(0)
    If Err Then
        Err.Clear
    End If

    If strFileName <> "" Then
        'Можно стирать файл с совпадающим именем, т.к. пользователь запросил его через синтаксис
        If Dir$(strFileName) <> "" Then
            Kill strFileName
        End If
        'но иногда его нельзя стереть, если файл открыт
        If Err = 10101 Then
            Err.Clear
            'если документ открыт в Excel, попробуем закрыть его и снова попытаемся стереть
            CloseOpenDocument strFileName
            Kill strFileName
            If Err Then
                'если ничего не помогает, откажемся от экспорта
                Err.Clear
                strFileName = ""
            End If
        End If
        GetFileName = strFileName
        Exit Function
    End If

    'Если в параметрах скрипта пусто, запросим имя файла у пользователя
    Do
        'получим путь и имя файла, куда будут сохранены результаты экспорта
        '3=означает подтверждение перезаписи существующего файла
        strFileName = GetFilePath$("Output.xls","xls",,SCRIPT_NAME, 3)
        If strFileName = "" Then    'пользователь отменил операцию
            Exit Function
        End If
        'пробуем стереть файл, т.к. пользователь дал "добро"
        If Dir$(strFileName) <> "" Then
            Kill strFileName
        End If
        'но иногда его нельзя стереть, если файл открыт
        If Err = 10101 Then
            ErrorBox "Файл """ & strFileName & _
                """ в настоящий момент открыт в Excel и не может быть перезаписан. " & _
                vbCrLf & vbCrLf & _
                "Пожалуйста, укажите другой файл, " & _
                "либо закройте этот и попробуйте ещё раз.", vbExclamation, _
                SCRIPT_NAME
            Err.Clear
            strFileName = ""
        ElseIf Err Then
            'не ясно, как обрабатывать другие возможные ошибки
            Err.Clear
            Exit Function
        End If
    Loop Until strFileName <> ""

    GetFileName = strFileName
End Function


Sub CloseOpenDocument(strFileName As String)
    On Error Resume Next
    CreateExcel
    objExcelApp.Workbooks(GetName(strFileName)).Activate
    objExcelApp.ActiveWorkbook.Close
    Err.Clear
End Sub


'возвращает ссылку на приложение Excel в глобальную переменную objExcelApp.
Sub CreateExcel()
    On Error Resume Next

    'GetObject возвращает ссылку на запущенное приложение.
    Set objExcelApp = GetObject(,"Excel.Application")
 '@ If Err = 10096 Then Debug.Print "Excel не запущен, используем CreateObject"
    'CreateObject запускает Excel, если тот к настоящему моменту не был запущен.
 '@ If objExcelApp Is Nothing Then
 '@     Set objExcelApp = CreateObject("Excel.Application")
 '@ End If
    'в случае, если надо диагностировать и другие ошибки
    Debug.Print Err; Err.Description
    Err.Clear

    If objExcelApp Is Nothing Then
        ErrorBox "Перед запуском скрипта откройте рабочую книгу Excel " & vbCrLf & _
            "и выделите ячейку, с которой желаете начать вставку результатов из SPSS.", vbExclamation, SCRIPT_NAME
        'Всегда восстанавливаем установки перед выходом!
        Alerts(ALERTS_RESTORE)
        End
    End If
    objExcelApp.Visible = True

    If objExcelApp.ActiveWorkbook Is Nothing Then
        ErrorBox "Не найтено открытых рабочих книг в Excel." & vbCrLf & _
           "Перед запуском скрипта откройте рабочую книгу Excel," & vbCrLf & _
           "и выделите ячейку, с которой желаете начать вставку результатов из SPSS.", vbExclamation, SCRIPT_NAME
       'Всегда восстанавливаем установки перед выходом!
       Alerts(ALERTS_RESTORE)
       End
    End If

    'objExcelApp.Workbooks.Add
    'objExcelApp.ActivateMicrosoftApp
End Sub


'Выделяем букву диска и путь к файлу из строки.
Function GetName(strFileName As String) As String
    Dim strName As String
    Dim intPos As Integer
    Dim intPos1 As Integer

    strName = strFileName

    'Выделяем букву диска и двоеточие, если имеется.
    intPos = InStr(strName, ":")
    If intPos > 0 Then
        strName = Mid$(strName, intPos + 1)
    End If

    'Находим последний обратный слэш \\.
    Do
        intPos = intPos1
        intPos1 = InStr(intPos1 + 1, strName, "\\")
    Loop Until intPos1 = 0

    'убираем всё до последнего обратного слэша \\.
    If intPos > 0 Then
        strName = Mid$(strName, intPos + 1)
    End If
    Debug.Print  strName

    'Убирать расширение не требуется...

    GetName = strName
End Function


'Обрабатываем свойство Alerts, которое приводит к ошибкам в SPSS 7.5.
'Вызываем с аргументом False (ложь) (ALERTS_PRESERVE) для инициализации.
'Вызываем с аргументом True (истина) (ALERTS_RESTORE) для восстановления установок
'перед завершением работы скрипта.
'Если скрипт вызван из синтаксиса, но, например, (ScriptParameter(0) <> ""),
'это подавляет выдачу предупреждений, которые будут останавливать исполнение.
Function Alerts(blnRestore As Boolean) As Boolean
    Static blnInitialized As Boolean
    Static blnAlerts As Boolean
    Static blnAlertsInitial As Boolean

    On Error Resume Next

    If Not blnInitialized Then
        blnInitialized = True

        blnAlertsInitial = objSpssApp.Alerts
        If Err Then 'spss 7.5
            blnAlertsInitial = True
            Err.Clear
        End If

        blnAlerts = (objSpssApp.ScriptParameter(0) = "")
        If Err Then 'spss 7.5
            blnAlerts = True
            Err.Clear
        End If
    End If

    If blnRestore Then
        objSpssApp.Alerts = blnAlertsInitial
        blnAlerts = blnAlertsInitial
        'blnInitialized = False
    End If

    Err.Clear
    Alerts = blnAlerts
End Function


'Обработчик вызова MsgBox: проверяет, разрешено ли выдавать сообщения
Function ErrorBox(strAlertMessage As String, intType As Integer, strTitle As String)
    On Error Resume Next
    Debug.Print strAlertMessage
    If Alerts(ALERTS_PRESERVE) Then
        ErrorBox = MsgBox(strAlertMessage, intType, strTitle)
    Else
        'Сообщения запрещены.
        ErrorBox = 0
    End If
End Function