2009-05-11 8 views
16

Jest to naprawdę super klasa diff hostowane przez Google tutaj:Jak mogę używać JavaScriptu w makrze programu Excel?

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

Używałem go wcześniej na kilku stronach internetowych, ale teraz muszę go używać ciągu Excel makro porównać tekst między dwiema komórkami.

Jest jednak dostępna tylko w językach JavaScript, Python, Java i C++, a nie VBA.

Moi użytkownicy są ograniczeni do programu Excel 2003, więc czyste rozwiązanie .NET nie będzie działać. Ręczne przetłumaczenie kodu na VBA zajęłoby zbyt dużo czasu i utrudniłoby aktualizację.

Jedną z opcji, którą rozważałem, było skompilowanie źródła JavaScript lub Java przy użyciu kompilatorów .NET (JScript.NET lub J #), użycie Reflectora do wyprowadzenia jako VB.NET, a następnie w końcu ręczne obniżenie kodu VB.NET do VBA, dając mi czyste rozwiązanie VBA. Po problemach z kompilacją z dowolnym kompilatorem .NET, porzuciłem tę ścieżkę.

Zakładając, że mogłem uzyskać działającą bibliotekę .NET, mogłem również użyć ExcelDna (http://www.codeplex.com/exceldna), open-source Excel, aby ułatwić integrację kodu .NET.

Mój ostatni pomysł polegał na obsłudze obiektu Internet Explorer, wysłaniu go źródłu kodu JavaScript i wywołaniu go. Nawet jeśli mam to do roboty, domyślam się, że byłby wolny od brudu i bałagan.

AKTUALIZACJA: Rozwiązanie zostało znalezione!

Użyłem metody WSC opisanej poniżej przez zaakceptowaną odpowiedź. Musiałem zmienić kod WSC trochę oczyścić dyferencjału i dać mi powrotem VBA zgodnego tablicy tablic:

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(); 
} 

Zarejestrowałem WSC i to działało dobrze. Kod w VBA dla nazywając ją następująco:

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 

(próbowałem utrzymanie jednolitego globalnego objWMIService i objDiff wokół, więc nie będzie musiał tworzyć/zniszczyć tych dla każdej komórki, ale nie wydaje aby zrobić różnicę w wydajności.)

Napisałem wtedy moje główne makro. Ma trzy parametry: zakres (jedna kolumna) oryginalnych wartości, zakres nowych wartości i zakres, w którym różnica powinna zrzucać wyniki. Wszystkie są zakłada, aby mieć taką samą liczbę wierszy, nie mam tutaj żadnego poważnego sprawdzania błędów.

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 

These najbliższych trzech liniach przyspieszyć aktualizację bez botching preferowany tryb obliczania użytkownika później:

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 

usuwając poprzednią dyferencjału, jeśli w ogóle, jest ważne:

  Erase diffs 

ten test jest wizualnym skrótem dla moich użytkowników, więc jasne jest, że nie ma żadnych zmian:

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

Kombajny cały tekst razem jako wartość komórki d, czy tekst był identyczny, wstawione lub usunięte:

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

Trzeba ustawić wartość przed rozpoczęciem formatowania:

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

Oto kod, który interpretuje dyferencjału i formatuje komórki d:

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 

Istnieje kilka możliwości optymalizacji, ale jak na razie wszystko działa dobrze. Dziękuję wszystkim, którzy pomogli!

+0

fajna. Cieszę się, że to działało dla ciebie. W przyszłości, jeśli chcesz, możesz odpowiedzieć na własne pytanie. Pojawi się w niebieskim polu tekstowym; wizualnie jest jasne, że je opublikowałeś. – Cheeso

+0

Projekt różnic/poprawek/poprawek Google zawiera teraz (w pełni zarządzany) port C#. –

Odpowiedz

11

Najprostszym podejściem może być osadzenie logiki JavaScript diff w komponencie COM bezpośrednio przy użyciu Javascript. Jest to możliwe poprzez coś zwanego "Windows Script Components".

Oto a tutorial on creating WSCs.

Składnik skryptów systemu Windows jest składnikiem COM zdefiniowanym w skrypcie. Interfejs do komponentu jest przez COM, co oznacza, że ​​jest przyjazny dla VBA. Logika jest zaimplementowana w każdym języku zgodnym z Windows Scripting Hosting, takim jak JavaScript lub VBScript. WSC jest zdefiniowany w pojedynczym pliku XML, który osadza logikę, identyfikator klasy komponentu, metody, logikę rejestracji i tak dalej.

Istnieje również tool available to help in creating a WSC. Zasadniczo jest to typ kreatora, który zadaje pytania i wypełnia szablon XML. Ja, zacząłem od przykładowego pliku .wsc i edytowałem go ręcznie za pomocą edytora tekstu. To dość oczywiste.

Komponent COM zdefiniowany w ten sposób w skrypcie (w pliku .wsc) można wywoływać tak samo, jak każdy inny składnik COM, z dowolnego środowiska, które może zatańczyć z COM.

UPDATE: Wziąłem kilka minut i wyprodukowałem WSC dla GoogleDiff. Oto jest.

<?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> 

Aby użyć tej rzeczy, musisz ją zarejestrować. W Eksploratorze kliknij prawym przyciskiem myszy i wybierz "Zarejestruj". lub z wiersza poleceń: plik regsvr32: \ c: \ scripts \ GoogleDiff.wsc

Nie próbowałem używać go z VBA, ale tutaj jest trochę kodu VBScript, który używa komponentu.

+0

Awesome. Zamierzam zrobić to, gdy dostanę szansę. W międzyczasie przyjmuję to jako najlepszą odpowiedź. – richardtallent

+0

Zamknij, ale diff_main zwraca tablicę różnic, z których każda jest tablicą dwuelementową z operatorem (równym, usuniętym lub wstawionym jako liczba całkowita) i tekstem. Wciąż pracuję nad tym, jak sprawić, aby VBA traktował wynik jako tablicę, dzięki czemu będę mógł go przejrzeć i utworzyć odpowiednie formatowanie w komórce programu Excel. – richardtallent

+0

Wiem, jaka logika javascript THINKS zwraca wartość. W moim teście VBScript typem zwracanej wartości jest String. Tak więc w moim przykładzie vbscript, podzieliłem ciąg znaków i ponownie zbudowałem tablicę "różnic". – Cheeso

2

Moja sugestia jest taka, że ​​cokolwiek zrobisz, zawiniesz w opakowaniu COM. VBA radzi sobie najlepiej z obiektami COM, więc możesz skompilować jako komponent .NET, a następnie eksponować go jako obiekt COM, korzystając z funkcji interopcji .NET.

Jako alternatywę można również sprawdzić obiekty hosta skryptów systemu Windows, aby wykonać plik JavaScript i zwrócić wynik.

+0

Możesz zrobić OBU. Za pomocą składników skryptów systemu Windows można zdefiniować składnik COM w JavaScript i wywołać komponent COM z VBA lub cokolwiek innego. – Cheeso

4

Dzięki Windows Scripting Engine będzie można uruchomić bibliotekę JavaScript. Działa dobrze w moim doświadczeniu.

+0

I pakując logikę Javascript jako składnik COM, przez to, co Microsoft wywołuje komponenty skryptów systemu Windows, będzie można łatwo wywołać skrypt JavaScript z Excel/VBA. – Cheeso

1

Oto kolejna opcja do rozważenia, choć w żaden sposób nie stwierdzam, że jest najlepsza.

  • Upewnij się, że wersja Pythona jest kompilowana w aplikacji IronPython. (Nie powinno tu być żadnych problemów, lub tylko niewielka ilość portowania.)
  • Utwórz bibliotekę dodatków Excel za pomocą C# i odwołaj się do IronPython.
  • Zawiń niezbędne funkcje w dodatku C# Excel.
+0

To dostanie rozwiązanie _all .Net_. Lubię to. –

+0

Chciałbym rozwiązanie all.NET, ALE utknąłem z programem Excel 2003. Również moi użytkownicy mogą mieć lub nie mieć określonej wersji środowiska wykonawczego .NET, więc preferowane jest rozwiązanie z wszystkimi VBA. – richardtallent

+0

W takim przypadku silnik skryptowy Windows (zgodny z COM) powinien pasować do rachunku. –

Powiązane problemy