Question

Il y a une classe diff vraiment cool hébergé par Google ici:

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

Je l'ai utilisé avant sur quelques sites web, mais maintenant je dois l'utiliser dans les une macro Excel pour comparer le texte entre les deux cellules.

Cependant, il est seulement disponible en JavaScript, Python, Java et C ++, VBA pas.

Mes utilisateurs sont limités à Excel 2003, donc une solution pure .NET ne fonctionne pas. Traduire le code VBA serait manuellement prendre trop de temps et faire la mise à niveau difficile.

Une option que je considérais était de compiler la source JavaScript ou Java en utilisant les compilateurs .NET (JScript.NET ou J #), utilisez réflecteur à la sortie comme VB.NET, puis finalement rétrograder manuellement le code VB.NET VBA, donnant moi une solution pure VBA. Après avoir des problèmes obtenir à compiler avec un compilateur .NET, j'abandonné ce chemin.

En supposant que je pourrais avoir obtenu une bibliothèque .NET de travail, je aurais pu utiliser ExcelDna ( http: // www. codeplex.com/exceldna ), un fichier Excel open source add-in pour faire de l'intégration de code .NET plus facile.

Ma dernière idée était d'accueillir un objet Internet Explorer, envoyer la source JavaScript, et de l'appeler. Même si je suis arrivé à ce travail, je suppose que ce serait lent saleté et désordre.

Mise à jour: Solution trouvée

J'ai utilisé la méthode décrite ci-dessous WSC par la réponse acceptée. Je devais changer le code WSC un peu pour nettoyer les diffs et me rendre un tableau compatible VBA de tableaux:

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

Je me suis inscrit le WSC et cela a fonctionné très bien. Le code en VBA pour l'appeler est la suivante:

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

(j'ai essayé en gardant une seule objWMIService mondiale et objDiff autour donc je ne voudrais pas créer / détruire ces derniers pour chaque cellule, mais il ne semble pas faire une différence sur la performance.)

J'ai alors écrit mon principal macro. Il faut trois paramètres: une plage (une colonne) des valeurs d'origine, une gamme de nouvelles valeurs, et une plage où la diff devrait vider les résultats. Tous sont supposé pour avoir le même nombre de la ligne, je n'ai aucune erreur de vérification sérieuse qui se passe ici.

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

Ces trois lignes suivantes accélèrent la mise à jour sans bousiller le mode de calcul préféré de l'utilisateur plus tard:

    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

Effacement des diffs précédentes, le cas échéant, est important:

            Erase diffs

Ce test est un raccourci visuel pour mes utilisateurs il est clair quand il n'y a pas de changement du tout:

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

Combiner tout le texte ensemble que la valeur de la cellule delta, si le texte était identique, inséré ou supprimé:

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

Vous devez définir la valeur avant à partir du formatage:

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

Voici le code qui interprète les diffs et formate la cellule 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

Il y a des possibilités d'optimisation, mais jusqu'à présent, il travaille très bien. Merci à tous ceux qui ont aidé!

Était-ce utile?

La solution

L'approche la plus simple peut être d'intégrer la logique de diff Javascript dans un composant COM directement en utilisant Javascript. Ceci est possible par quelque chose appelé « Composants Windows Script ».

Voici un tutoriel sur la création WSC .

Un composant de script Windows est un composant COM qui est défini dans le script. L'interface du composant est par COM, ce qui signifie qu'il est facile de VBA. La logique est mis en œuvre dans tous les scripts Hébergement Windows langage compatible, comme JavaScript ou VBScript. Le WSC est défini dans un fichier XML unique, qui intègre la logique, le composant ID de classe, les méthodes, la logique d'enregistrement, et ainsi de suite.

Il y a aussi un outil rel="noreferrer"> WSC. Fondamentalement, il est une chose de type assistant qui vous demande des questions et remplit le modèle XML. Moi-même, je viens de commencer avec un exemple de fichier .wsc et édité par la main avec un éditeur de texte. Il est assez explicite.

Un composant COM défini ainsi dans le script (dans un fichier .wsc) est appelable comme tout autre composant COM, de tout environnement qui peut danser avec COM.

UPDATE : Je pris quelques minutes et produit le WSC pour GoogleDiff. Ici, il est.

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

Pour utiliser cette chose, vous devez l'enregistrer. Dans l'Explorateur, faites un clic droit dessus et sélectionnez « Enregistrer ». ou, à partir de la ligne de commande:     fichier regsvr32: \ c: \ scripts \ GoogleDiff.wsc

Je n'ai pas essayé de l'utiliser de VBA, mais voici un code VBScript qui utilise le composant.

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

Autres conseils

Le Windows Scripting le moteur vous permettra d'exécuter la bibliothèque JavaScript. Il fonctionne bien dans mon expérience.

Ma suggestion serait que quoi que vous fassiez vous l'envelopper dans un emballage COM. VBA traite mieux avec les objets COM de sorte que vous pouvez compiler en tant que composant .NET puis exposer comme un objet COM en utilisant la fonctionnalité Interop .NET.

Comme alternative, vous pouvez également regarder dans les objets Windows Scripting Host pour exécuter un fichier Javascript et vous retourner le résultat.

Voici une autre option à considérer, bien que je ne suis pas par tout moyen indiquant son le meilleur.

  • S'assurer que la version Python compile en IronPython. (Il ne devrait pas y avoir de problèmes ici, ou seulement une petite quantité de portage au plus.)
  • Créer une bibliothèque add-in Excel utilisant C # et référence IronPython de celui-ci.
  • Enveloppez les fonctionnalités nécessaires dans votre add-in Excel C #.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top