2009-05-11 5 views
16

Там действительно крутой класс дифф организованный Google здесь:Как я могу использовать JavaScript в макросе Excel?

http://code.google.com/p/google-diff-match-patch/

Я использовал его раньше на нескольких веб-сайтов, но теперь мне нужно использовать его в Excel макрос для сравнения текста между двумя ячейками.

Однако он доступен только на JavaScript, Python, Java и C++, а не на VBA.

Мои пользователи ограничены Excel 2003, поэтому чистое .NET-решение не будет работать. Перевод кода на VBA вручную займет слишком много времени и затруднит процесс обновления.

Один из вариантов, который я рассматривал, заключался в том, чтобы скомпилировать исходный код JavaScript или Java с помощью компиляторов .NET (JScript.NET или J #), использовать Reflector для вывода в виде VB.NET, а затем, наконец, перевести код VB.NET вручную в VBA, давая мне чистое решение VBA. После того, как у меня возникли проблемы с его компиляцией с любым компилятором .NET, я отказался от этого пути.

Предполагая, что я мог бы получить рабочую библиотеку .NET, я мог бы также использовать ExcelDna (http://www.codeplex.com/exceldna), надстройку Excel с открытым кодом, чтобы упростить интеграцию кода .NET.

Моя последняя идея состояла в том, чтобы разместить объект Internet Explorer, отправить его исходный код JavaScript и вызвать его. Даже если у меня это получится, я предполагаю, что это будет грязно-медленно и беспорядочно.

ОБНОВЛЕНИЕ: Решение найдено!

Я использовал метод WSC, описанный ниже, принятым ответом. Я должен был изменить WSC код немного, чтобы очистить и переформатирование и дать мне обратно VBA-совместимый массив массивов:

function DiffFast(text1, text2) 
{ 
    var d = dmp.diff_main(text1, text2, true); 
    dmp.diff_cleanupSemantic(d); 
    var dictionary = new ActiveXObject("Scripting.Dictionary"); // VBA-compatible array 
    for (var i = 0; i < d.length; i++) { 
    dictionary.add(i, JS2VBArray(d[i])); 
    } 
    return dictionary.Items(); 
} 

function JS2VBArray(objJSArray) 
{ 
    var dictionary = new ActiveXObject("Scripting.Dictionary"); 
    for (var i = 0; i < objJSArray.length; i++) { 
     dictionary.add(i, objJSArray[ i ]); 
     } 
    return dictionary.Items(); 
} 

Я зарегистрировал WSC и он работал нормально. Код в VBA для вызова его заключается в следующем:

Public Function GetDiffs(ByVal s1 As String, ByVal s2 As String) As Variant() 
    Dim objWMIService As Object 
    Dim objDiff As Object 
    Set objWMIService = GetObject("winmgmts:") 
    Set objDiff = CreateObject("Google.DiffMatchPath.WSC") 
    GetDiffs = objDiff.DiffFast(s1, s2) 
    Set objDiff = Nothing 
    Set objWMIService = Nothing 
End Function 

(я пытался держать один глобальный objWMIService и objDiff вокруг, так что я не должен был бы создать/разрушить их для каждой ячейки, но это не похоже, чтобы повлиять на производительность.)

Я тогда написал свой основной макрос. Он принимает три параметра: диапазон (один столбец) исходных значений, диапазон новых значений и диапазон, в котором diff должен сбрасывать результаты. Все из предположили, что имеют одинаковое количество строк, никаких серьезных проверок ошибок здесь нет.

Public Sub DiffAndFormat(ByRef OriginalRange As Range, ByRef NewRange As Range, ByRef DeltaRange As Range) 
    Dim idiff As Long 
    Dim thisDiff() As Variant 
    Dim diffop As String 
    Dim difftext As String 
    difftext = "" 
    Dim diffs() As Variant 
    Dim OriginalValue As String 
    Dim NewValue As String 
    Dim DeltaCell As Range 
    Dim row As Integer 
    Dim CalcMode As Integer 

Этой следующей три строки ускорить обновление без botching предпочтительного режима вычисления пользователя позже:

Application.ScreenUpdating = False 
    CalcMode = Application.Calculation 
    Application.Calculation = xlCalculationManual 
    For row = 1 To OriginalRange.Rows.Count 
     difftext = "" 
     OriginalValue = OriginalRange.Cells(row, 1).Value 
     NewValue = NewRange.Cells(row, 1).Value 
     Set DeltaCell = DeltaRange.Cells(row, 1) 
     If OriginalValue = "" And NewValue = "" Then 

Стирание предыдущего посмотреть различие, если таковые имеются, важно:

  Erase diffs 

Этого test - это визуальный ярлык для моих пользователей, поэтому ясно, когда изменений нет:

 ElseIf OriginalValue = NewValue Then 
      difftext = "No change." 
      Erase diffs 
     Else 

Объединить весь текст вместе в качестве значения дельта-клеток, был ли текст идентичен, вставленный или удалены:

  diffs = GetDiffs(OriginalValue, NewValue) 
      For idiff = 0 To UBound(diffs) 
       thisDiff = diffs(idiff) 
       difftext = difftext & thisDiff(1) 
      Next 
     End If 

Вы должны установить значение перед тем началом форматирования:

 DeltaCell.value2 = difftext 
     Call FormatDiff(diffs, DeltaCell) 
    Next 
    Application.ScreenUpdating = True 
    Application.Calculation = CalcMode 
End Sub 

Вот код, который интерпретирует и переформатирование и форматирует дельта клетки:

Public Sub FormatDiff(ByRef diffs() As Variant, ByVal cell As Range) 
    Dim idiff As Long 
    Dim thisDiff() As Variant 
    Dim diffop As String 
    Dim difftext As String 
    cell.Font.Strikethrough = False 
    cell.Font.ColorIndex = 0 
    cell.Font.Bold = False 
    If Not diffs Then Exit Sub 
    Dim lastlen As Long 
    Dim thislen As Long 
    lastlen = 1 
    For idiff = 0 To UBound(diffs) 
     thisDiff = diffs(idiff) 
     diffop = thisDiff(0) 
     thislen = Len(thisDiff(1)) 
     Select Case diffop 
      Case -1 
       cell.Characters(lastlen, thislen).Font.Strikethrough = True 
       cell.Characters(lastlen, thislen).Font.ColorIndex = 16 ' Dark Gray http://www.microsoft.com/technet/scriptcenter/resources/officetips/mar05/tips0329.mspx 
      Case 1 
       cell.Characters(lastlen, thislen).Font.Bold = True 
       cell.Characters(lastlen, thislen).Font.ColorIndex = 32 ' Blue 
     End Select 
     lastlen = lastlen + thislen 
    Next 
End Sub 

Есть некоторые возможности для оптимизации, но пока это работает нормально. Спасибо всем, кто помог!

+0

прохладный. Рад, что это сработало для вас. В будущем, если хотите, вы можете ответить на свой вопрос. Он появится в синем текстовом поле; визуально ясно, что вы разместили его. – Cheeso

+0

Проект Google diff/merge/patch теперь включает (полностью управляемый) порт C#. –

ответ

11

Простейший подход может заключаться в том, чтобы встроить Javascript diff-логику в COM-компонент непосредственно с помощью Javascript. Это возможно через нечто, называемое «Windows Script Components».

Адрес a tutorial on creating WSCs.

Компонент Windows Script - это COM-компонент, определенный в скрипте. Интерфейс с компонентом осуществляется через COM, что означает, что он совместим с VBA. Логика реализована на любом совместимом с Windows Scripting Hosting языке, таком как JavaScript или VBScript. WSC определяется в одном XML-файле, который включает в себя логику, идентификатор класса компонента, методы, логику регистрации и т. Д.

Существует также tool available to help in creating a WSC. В основном это тип типа мастера, который задает вам вопросы и заполняет XML-шаблон. Я сам начал с примера .wsc-файла и отредактировал его вручную с помощью текстового редактора. Это довольно понятно.

Компонент COM, определенный таким образом в скрипте (в файле .wsc), может быть вызван так же, как любой другой компонент COM, из любой среды, которая может танцевать с COM.

ОБНОВЛЕНИЕ: Я взял несколько минут и создал WSC для GoogleDiff. Вот.

<?xml version="1.0"?> 

<package> 

<component id="Cheeso.Google.DiffMatchPatch"> 

    <comment> 
    COM Wrapper on the Diff/Match/Patch logic published by Google at http://code.google.com/p/google-diff-match-patch/. 
    </comment> 

<?component error="true" debug="true"?> 

<registration 
    description="WSC Component for Google Diff/Match/Patch" 
    progid="Cheeso.Google.DiffMatchPatch" 
    version="1.00" 
    classid="{36e400d0-32f7-4778-a521-2a5e1dd7d11c}" 
    remotable="False"> 

    <script language="VBScript"> 
    <![CDATA[ 

    strComponent = "Cheeso's COM wrapper for Google Diff/Match/Patch" 

    Function Register 
     MsgBox strComponent & " - registered." 
    End Function 

    Function Unregister 
     MsgBox strComponent & " - unregistered." 
    End Function 

    ]]> 
    </script> 
</registration> 


<public> 
    <method name="Diff"> 
    <parameter name="text1"/> 
    <parameter name="text2"/> 
    </method> 
    <method name="DiffFast"> 
    <parameter name="text1"/> 
    <parameter name="text2"/> 
    </method> 
</public> 


<script language="Javascript"> 
<![CDATA[ 


    // insert original google diff code here... 


// public methods on the component 
var dpm = new diff_match_patch(); 


function Diff(text1, text2) 
{ 
    return dpm.diff_main(text1, text2, false); 
} 


function DiffFast(text1, text2) 
{ 
    return dpm.diff_main(text1, text2, true); 
} 


]]> 
</script> 

</component> 

</package> 

Чтобы использовать эту вещь, вам необходимо ее зарегистрировать. В проводнике щелкните правой кнопкой мыши по нему и выберите «Регистрация». или из командной строки: regsvr32 file: \ c: \ scripts \ GoogleDiff.wsc

Я не пытался использовать его из VBA, но вот код VBScript, который использует этот компонент.

Sub TestDiff() 
    dim t1 
    t1 = "The quick brown fox jumped over the lazy dog." 

    dim t2 
    t2 = "The large fat elephant jumped over the cowering flea." 

    WScript.echo("") 

    WScript.echo("Instantiating a Diff Component ...") 
    dim d 
    set d = WScript.CreateObject("Cheeso.Google.DiffMatchPatch") 

    WScript.echo("Doing the Diff...") 
    x = d.Diff(t1, t2) 

    WScript.echo("") 
    WScript.echo("Result was of type: " & TypeName(x)) 
    ' result is all the diffs, joined by commas. 
    ' Each diff is an integer (position), and a string. These are separated by commas. 
    WScript.echo("Result : " & x) 

    WScript.echo("Transform result...") 
    z= Split(x, ",") 
    WScript.echo("") 
    redim diffs(ubound(z)/2) 
    i = 0 
    j = 0 
    For Each item in z 
     If (j = 0) then 
     diffs(i) = item 
     j = j+ 1  
     Else 
      diffs(i) = diffs(i) & "," & item 
     i = i + 1 
     j = 0 
     End If 
    Next 

    WScript.echo("Results:") 
    For Each item in diffs 
     WScript.echo(" " & item) 
    Next 

    WScript.echo("Done.") 

End Sub 
+0

Awesome. Я собираюсь сделать это, когда у меня появится шанс. Тем временем я приму это как лучший ответ. – richardtallent

+0

Закрыть, но diff_main возвращает массив различий, каждый из которых представляет собой двухэлементный массив с оператором (равным, удаленным или вставленным как целое число) и текстом. Я все еще работаю над тем, как получить VBA для обработки результата в виде массива, чтобы я мог выполнить его и создать соответствующее форматирование в ячейке Excel. – richardtallent

+0

Я знаю, что логика JavaScript обрабатывает возвращаемое значение. В моем тесте VBScript тип возвращаемого значения - String. Поэтому в моем примере vbscript я разделил строку и перестроил массив «различий». – Cheeso

2

Мое предложение состояло бы в том, чтобы что бы вы ни делали, вы завертываете его в COM-обертку. VBA лучше всего подходит для COM-объектов, поэтому вы можете компилировать их как .NET-компонент, а затем выставлять в виде COM-объекта, используя функциональность взаимодействия .NET.

В качестве альтернативы вы также можете изучить использование объектов Windows Scripting Host для выполнения Javascript-файла и вернуть результат.

+0

Вы можете сделать ОБА. Используя компоненты сценария Windows, вы можете определить свой COM-компонент в Javascript и вызвать компонент COM из VBA или что-то еще. – Cheeso

4

Windows Scripting Engine позволит вам запустить библиотеку JavaScript. Это хорошо работает в моем опыте.

+0

И, упаковывая логику Javascript в качестве COM-компонента, через эту вещь Microsoft называет компоненты Windows Script, легко будет вызывать Javascript из Excel/VBA. – Cheeso

1

Вот еще один вариант рассмотреть, хотя я никоим образом не заявляю, что он лучший.

  • Убедитесь, что версия Python скомпилирована в IronPython. (Здесь не должно быть никаких проблем или всего лишь небольшое количество портирования.)
  • Создайте библиотеку надстроек Excel, используя C# и ссылку IronPython.
  • Оберните необходимые функции в надстройку надстройки C# Excel.
+0

Это получило бы решение _all .Net_. Мне это нравится. –

+0

Мне понравилось бы решение all-.NET, НО я застрял в Excel 2003. Кроме того, мои пользователи могут иметь или не иметь определенную версию среды выполнения .NET, поэтому предпочтительным является решение all-VBA. – richardtallent

+0

В этом случае Windows-скриптовый движок (COM-based) должен соответствовать счету. –