Comment puis-je utiliser JavaScript dans une macro Excel?
-
21-08-2019 - |
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é!
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 #.