Pregunta

Hay una clase de diferencias muy fresco alojado por Google aquí:

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

Lo he utilizado antes en algunos sitios web, pero ahora tengo que usarlo en una macro de Excel para comparar el texto entre dos células.

Sin embargo, sólo está disponible en JavaScript, Python, Java y C ++, no VBA.

Mis usuarios se limitan a Excel 2003, por lo que una solución .NET pura no funcionarían. Traducción del código de VBA de forma manual llevaría demasiado tiempo y la actualización sea difícil.

Una opción que consideré era compilar la fuente JavaScript o Java utilizando los compiladores .NET (JScript.NET o J #), utilizar el reflector a la salida como VB.NET, y finalmente rebajar el código VB.NET manualmente a VBA, dando yo una solución de VBA puro. Después de tener problemas para conseguir que al compilar con cualquier compilador .NET, abandoné este camino.

Si se asume que podría haber conseguido una biblioteca .NET de trabajo, que también podría haber utilizado ExcelDna ( http: // www. codeplex.com/exceldna ), un código abierto complemento de Excel para que la integración de código .NET más fácil.

Mi última idea fue el anfitrión de un objeto de Internet Explorer, enviar la fuente de JavaScript, y decir que es. Incluso si llegué a este trabajo, yo creo que sería suciedad lento y desordenado.

ACTUALIZACIÓN: Solución encontrados

I utilizado el método WSC se describe a continuación por la respuesta aceptada. Tenía que cambiar el código de WSC un poco para limpiar los diferenciales y me devuelva una matriz compatible con VBA de matrices:

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

Me he registrado la CSM y funcionó muy bien. El código en VBA para llamar es de la siguiente manera:

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

(He intentado mantener un único objWMIService global y objDiff alrededor, así que no tendría que crear / destruir estos para cada celda, pero no parece que hacer una diferencia en el rendimiento.)

Entonces escribí mi macro principal. Se necesitan tres parámetros: un rango (una columna) de los valores originales, una serie de nuevos valores, y un rango donde el diff debe volcar los resultados. Todos son asumido a tener el mismo número de la fila, no tengo ninguna seria de comprobación de errores pasando aquí.

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

Las siguientes tres líneas de acelerar la actualización sin estropear modo de cálculo preferido del usuario después:

    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

Borrado de los diferenciales anteriores, en su caso, es importante:

            Erase diffs

Esta prueba es un atajo visual para mis usuarios por lo que es claro cuando no hay ningún cambio en absoluto:

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

Combinar todo el texto juntos como el valor de la celda delta, si el texto era idéntico, insertado o eliminado:

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

Hay que establecer el valor de antes a partir del formato:

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

Este es el código que interpreta los diferenciales y formatea la célula 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

Hay algunas oportunidades para la optimización, pero hasta ahora está funcionando muy bien. Gracias a todos los que ayudaron!

¿Fue útil?

Solución

El enfoque más simple puede ser para incrustar la lógica diff Javascript en un componente COM directamente utilizando Javascript. Esto es posible a través de algo llamado " componentes de la escritura de Windows ".

Aquí está un tutorial sobre la creación de WSC .

Un componente de script de Windows es un componente COM que se define en la escritura. La interfaz para el componente es a través de COM, lo que significa que es amigable VBA. La lógica se implementa en cualquier Windows Scripting Hosting lenguaje compatible, como JavaScript o VBScript. El CSM se define en un solo archivo XML, que incorpora la lógica, el ID de clase de componente, los métodos, la lógica de registro, y así sucesivamente.

Hay también una herramienta rel="noreferrer"> CSM. Básicamente se trata de una cosa de tipo asistente que le hace preguntas y rellena la plantilla XML. Yo, acabo de empezar con un ejemplo de archivo .wsc y editado manualmente con un editor de texto. Es bastante auto-explicativo.

Un componente COM definido de esta manera en la escritura (en un archivo .wsc) es exigible al igual que cualquier otro componente COM, desde cualquier entorno que puede bailar con COM.

Actualizar : Di unos minutos y produjo el CSM para GoogleDiff. Aquí 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>

Para utilizar esa cosa, usted tiene que registrarlo. En el Explorador, haga clic derecho sobre él y seleccione "Registro". o, desde la línea de comandos:     archivo regsvr32: \ c: \ scripts \ GoogleDiff.wsc

no lo probé usando desde VBA, pero aquí hay un código de VBScript que utiliza el 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

Otros consejos

El Windows Scripting motor le permitirá ejecutar la biblioteca JavaScript. Funciona bien en mi experiencia.

Mi sugerencia sería que cualquier cosa que hagas en que se coloca en un contenedor COM. VBA trata mejor con objetos COM por lo que podría compilar como un componente .NET luego exponer como un objeto COM utilizando la funcionalidad de interoperabilidad de .NET.

Como alternativa también se puede ver en el uso de objetos de Windows Scripting Host para ejecutar un archivo JavaScript y devolver el resultado.

Aquí hay otra opción a considerar, aunque no estoy de ninguna manera que indique su el mejor.

  • Asegúrese que la versión de Python compila en IronPython. (No debería haber ningún problema aquí, o sólo una pequeña cantidad de portar como máximo.)
  • Crear un complemento de Excel en la biblioteca usando C # y referencia IronPython de ella.
  • Envolver la funcionalidad necesaria en su C # complemento de Excel.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top