Come posso usare JavaScript all'interno di una macro di Excel?
-
21-08-2019 - |
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!
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.