Pergunta

Há um muito legal diff classe hospedado pelo Google aqui:

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

Eu usei isso antes em alguns sites, mas agora eu preciso usá-lo em uma macro do Excel para comparar texto entre duas células.

No entanto, ela só está disponível em JavaScript, Python, Java e C ++, não VBA.

Meus usuários estão limitados a Excel 2003, então uma solução pura .NET não iria funcionar. Traduzindo o código para VBA manualmente levaria muito tempo e fazer a atualização difícil.

Uma opção I considerado foi de compilar o código JavaScript ou Java utilizando os compiladores .NET (JScript.NET ou J #), o uso do refletor para a saída como VB.NET, então finalmente rebaixar o código VB.NET manualmente para VBA, dando me uma solução VBA puro. Depois de ter problemas começá-lo para compilar com qualquer compilador .NET, abandonei este caminho.

Supondo que eu poderia ter obtido uma biblioteca .NET trabalhar, eu poderia ter usado também ExcelDna ( http: // www. codeplex.com/exceldna ), um open-source Excel add-in para fazer a integração de código .NET mais fácil.

A minha última ideia era para hospedar um objeto Internet Explorer, enviá-lo a fonte de JavaScript, e chamá-lo. Mesmo se eu tenho isso para trabalho, o meu palpite é que seria dirt-lento e confuso.

UPDATE: Solução encontrada

!

Eu usei o método WSC descrito a seguir pela resposta aceite. Eu tive que mudar o código WSC um pouco para limpar os diffs e me devolver uma matriz VBA-compatível de matrizes:

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

I registrou a WSC e funcionou muito bem. O código em VBA para chamá-lo é a seguinte:

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

(eu tentei manter um único objWMIService global e objDiff em torno de modo que eu não teria que criar / destruir estes para cada célula, mas não parecem fazer a diferença no desempenho.)

Eu, então, escreveu o meu principal macro. Leva três parâmetros: um intervalo (coluna um) dos valores originais, uma gama de novos valores, e uma gama onde o diff deve despejar os resultados. Todos são assumiu ter o mesmo número de linha, eu não tenho nenhuma séria acontecendo aqui de verificação de erros.

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

As próximas três linhas de acelerar a atualização sem botching modo de cálculo preferido do usuário mais tarde:

    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

Apagar os diffs anteriores, se houver, é importante:

            Erase diffs

Este teste é um atalho visual para meus usuários, por isso é claro quando não há nenhuma mudança em tudo:

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

Combine todo o texto em conjunto, como o valor da célula delta, se o texto era idêntico, inseridos ou apagados:

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

Você tem que definir o valor antes de iniciar a formatação:

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

Aqui está o código que interpreta o diffs e formatos a células 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

Existem algumas oportunidades de otimização, mas até agora ele está funcionando muito bem. Obrigado a todos que ajudaram!

Foi útil?

Solução

A abordagem mais simples pode ser para incorporar a lógica diff Javascript em um componente COM diretamente usando Javascript. Isto é possível através de algo chamado " Windows Script Components ".

Aqui está um tutorial sobre como criar WSCs .

A Windows Script Component é um componente COM que é definido no script. A interface para o componente é via COM, o que significa que é VBA amigável. A lógica é implementada em qualquer Windows Scripting Hospedando linguagem -compatível, como JavaScript ou VBScript. A WSC é definido em um único arquivo XML, que incorpora a lógica, o componente Classe ID, os métodos, a lógica de registo, e assim por diante.

Há também um href="http://www.microsoft.com/downloads/details.aspx?familyid=408024ED-FAAD-4835-8E68-773CCC951A6B&displaylang=en" ferramenta disponível para ajuda na criação de uma WSC . Basicamente, é uma coisa do tipo assistente que faz perguntas e preenche o modelo XML. Eu mesmo, eu só começou com um arquivo de exemplo .wsc e editado à mão com um editor de texto. É bastante auto-explicativo.

A COM componente definido desta forma, em roteiro (em um arquivo .wsc) é que pode ser chamado como qualquer outro componente COM, a partir de qualquer ambiente que pode dançar com COM.

Atualizar : I levou alguns minutos e produziu a WSC para GoogleDiff. Aqui 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 usar essa coisa, você tem que registrá-lo. No Explorer, clique direito sobre ele e selecione "Register". ou, a partir da linha de comando: arquivo regsvr32: \ c: \ scripts \ GoogleDiff.wsc

Eu não tente usá-lo da VBA, mas aqui é algum código VBScript que utiliza o 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

Outras dicas

O Windows Scripting motor lhe permitirá executar a biblioteca JavaScript. Ele funciona bem em minha experiência.

A minha sugestão seria a de que o que quer que você envolvê-la em um invólucro COM. VBA lida melhor com o COM objetos para que você possa compilar como um Componente .NET, em seguida, expor como um objeto COM usando a funcionalidade de interoperabilidade do .NET.

Como alternativa, você também pode olhar para utilizar o Windows Scripting Host objetos para executar um arquivo JavaScript e retornar o resultado.

Aqui está uma outra opção a considerar, embora eu não sou, por qualquer meio que indica a sua a melhor.

  • Garantir que a versão Python compila em IronPython. (Não deve haver nenhum problema aqui, ou apenas uma pequena quantidade de portar no máximo.)
  • Criar um add-in biblioteca Excel usando C # e referência IronPython com isso.
  • Enrole a funcionalidade necessária em sua C # Excel add-in.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top