Domanda

C'è una classe diff davvero cool ospitato da Google qui:

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

ho usato prima su alcuni siti web, ma ora ho bisogno di usarlo nel una macro di Excel per confrontare il testo tra due celle.

Tuttavia, è disponibile solo in JavaScript, Python, Java e C ++, non VBA.

I miei utenti sono limitati a Excel 2003, quindi una soluzione .NET puro non funzionerebbero. Traducendo il codice VBA manualmente richiederebbe troppo tempo e rendere l'aggiornamento difficile.

Una possibilità ho considerato è stato quello di compilare il sorgente JavaScript o Java utilizzando i compilatori .NET (JScript.NET o J #), utilizzare Reflector per uscita come VB.NET, poi finalmente declassare il codice VB.NET manualmente a VBA, dando me una soluzione pura VBA. Dopo aver problemi a farla compilare con qualsiasi compilatore .NET, ho abbandonato questo percorso.

Supponendo che ho potuto trovare una libreria .NET di lavoro, avrei potuto usare anche ExcelDna ( http: // www. codeplex.com/exceldna ), un open-source Excel add-in per rendere l'integrazione di codice .NET facile.

La mia ultima idea era quella di ospitare un oggetto di Internet Explorer, inviare la sorgente JavaScript, e chiamarlo. Anche se ho ottenuto questo al lavoro, la mia ipotesi è che sarebbe sporco lento e disordinato.

UPDATE: Soluzione trovati

Ho usato il metodo descritto di seguito WSC per la risposta accettata. Ho dovuto cambiare il codice WSC un po 'per ripulire le diff e ridammi una matrice compatibile con VBA di array:

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

ho registrato il WSC e ha funzionato bene. Il codice in VBA per chiamare è il seguente:

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

(ho provato mantenendo un unico objWMIService globale e objDiff intorno in modo da non dover creare / distruggere questi per ogni cella, ma non mi sembra di fare la differenza sulle prestazioni.)

Poi ho scritto la mia macro principale. Ci vogliono tre parametri: un intervallo (una colonna) dei valori originali, una gamma di nuovi valori, e un intervallo in cui il diff dovrebbe eseguire il dump dei risultati. Tutti sono assunto di avere lo stesso numero di fila, io non ho alcun serio controllo degli errori succedendo qui.

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

Questi tre righe successive accelerare l'aggiornamento senza botching modo di calcolo preferito dell'utente in seguito:

    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

Cancellazione dei diff precedenti, se esiste, è importante:

            Erase diffs

Questo test è una scorciatoia visiva per i miei utenti in modo che sia chiaro quando non c'è alcun cambiamento a tutti:

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

Combinate tutto il testo insieme, come il valore della cella delta, se il testo è stato identico, inserito o eliminato:

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

È necessario impostare il valore di prima di iniziare la formattazione:

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

Ecco il codice che interpreta le diff e formatta la cella di delta:

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

Ci sono alcune opportunità di ottimizzazione, ma finora sta funzionando bene. Grazie a tutti coloro che hanno contribuito!

È stato utile?

Soluzione

L'approccio più semplice può essere quello di incorporare la logica diff JavaScript in un componente COM direttamente utilizzando Javascript. Questo è possibile tramite una cosa chiamata " Componenti di Windows Script ".

Ecco un tutorial sulla creazione di WSC .

Una di Windows Script Component è un componente COM che è definito nello script. L'interfaccia per il componente è via COM, che significa che è VBA amichevole. La logica è implementata in qualsiasi lingua Hosting compatibile Windows Scripting, come JavaScript o VBScript. Il WSC è definito in un unico file XML, che incorpora la logica, l'ID di classe dei componenti, i metodi, la logica di registrazione, e così via.

C'è anche uno strumento rel="noreferrer"> . Fondamentalmente si tratta di una procedura guidata di tipo cosa che pone domande e compila il modello XML. Io stesso, ho appena iniziato con un file di esempio .wsc e modificati a mano con un editor di testo. E 'piuttosto auto-esplicativo.

Un componente COM definito in questo modo nello script (in un file .wsc) è richiamabile proprio come qualsiasi altro componente COM, da qualsiasi ambiente che può ballare con COM.

Aggiorna : ho preso un paio di minuti e prodotto la WSC per GoogleDiff. Eccolo.

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

Per usare quella cosa, è necessario registrarlo. In Explorer, fate clic destro su di esso e selezionare "Register". o, dalla riga di comando:     il file regsvr32: \ c: \ scripts \ GoogleDiff.wsc

non ho provato ad usarlo da VBA, ma qui è un codice VBScript che utilizza il componente.

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

Altri suggerimenti

Il Windows Scripting motore vi permetterà di eseguire la libreria JavaScript. Funziona bene nella mia esperienza.

Il mio suggerimento sarebbe che qualunque cosa tu faccia si avvolge in un wrapper COM. VBA migliore offerta con gli oggetti COM e quindi si può compilare come un componente .NET poi esporre come un oggetto COM utilizzando la funzionalità di interoperabilità di .NET.

In alternativa si potrebbe anche prendere in considerazione l'uso di oggetti di Windows Scripting Host per eseguire un file JavaScript e restituire il risultato.

Ecco un'altra opzione da considerare, anche se io non sono con qualsiasi mezzo affermando la sua la migliore.

  • Assicurare che la versione di Python compila in IronPython. (Non ci dovrebbero essere problemi qui, o solo una piccola quantità di porting al massimo.)
  • Crea un componente aggiuntivo di Excel in libreria utilizzando C # e riferimento IronPython da esso.
  • Avvolgere le funzionalità necessarie in C # Excel add-in.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top