【问题标题】:How can I use JavaScript within an Excel macro?如何在 Excel 宏中使用 JavaScript?
【发布时间】:2010-10-25 07:07:42
【问题描述】:

这里有一个由 Google 托管的非常酷的 diff 类:

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

我之前在一些网站上使用过它,但现在我需要在一个 Excel 宏中使用它来比较两个单元格之间的文本。

但是,它仅适用于 JavaScript、Python、Java 和 C++,不适用于 VBA。

我的用户仅限于 Excel 2003,因此纯 .NET 解决方案无法工作。手动将代码转换为 VBA 会花费太多时间,并且会导致升级困难。

我考虑的一个选项是使用 .NET 编译器(JScript.NET 或 J#)编译 JavaScript 或 Java 源代码,使用 Reflector 输出为 VB.NET,最后手动将 VB.NET 代码降级为 VBA,给出我是一个纯 VBA 解决方案。在使用任何 .NET 编译器进行编译时遇到问题后,我放弃了这条路。

假设我可以获得一个工作的 .NET 库,我也可以使用 ExcelDna (http://www.codeplex.com/exceldna),这是一个开源 Excel 插件,可以更轻松地集成 .NET 代码。

我的最后一个想法是托管一个 Internet Explorer 对象,将 JavaScript 源发送给它,然后调用它。即使我让这个工作,我猜它会非常缓慢和混乱。

更新:找到解决方案!

我使用了下面接受的答案描述的 WSC 方法。我不得不稍微更改 WSC 代码以清理差异并返回与 VBA 兼容的数组数组:

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

我注册了 WSC,它运行良好。 VBA中调用它的代码如下:

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

(我尝试保留一个全局 objWMIService 和 objDiff,这样我就不必为每个单元创建/销毁它们,但它似乎对性能没有影响。)

然后我编写了我的主宏。它需要三个参数:原始值的范围(一列)、新值的范围以及 diff 应该转储结果的范围。所有假定都具有相同的行数,我这里没有进行任何严重的错误检查。

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

接下来的三行可以加快更新速度,而不会影响用户的首选计算模式:

    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

删除以前的差异(如果有)很重要:

            Erase diffs

这个测试对我的用户来说是一个可视化的快捷方式,所以当没有任何变化时很清楚:

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

将所有文本组合在一起作为增量单元格值,无论文本是相同的、插入的还是删除的:

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

您必须在开始格式化之前设置值

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

这是解释差异和格式化增量单元格的代码:

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

有一些优化的机会,但到目前为止它工作得很好。感谢所有帮助过的人!

【问题讨论】:

  • 酷。很高兴它对你有效。将来,如果您愿意,可以回答自己的问题。它将在蓝色文本框中弹出;从视觉上看,您已经发布了它。
  • Google diff/merge/patch 项目现在包括一个(完全托管的)C# 端口。

标签: .net javascript excel vba j#


【解决方案1】:

最简单的方法可能是直接使用 Javascript 将 Javascript 差异逻辑嵌入到 COM 组件中。这可以通过称为“Windows Script Components”的东西来实现。

这里是a tutorial on creating WSCs

Windows 脚本组件是在脚本中定义的 COM 组件。组件的接口是通过 COM,这意味着它是 VBA 友好的。该逻辑以任何与 Windows Scripting Hosting 兼容的语言实现,例如 JavaScript 或 VBScript。 WSC 在单个 XML 文件中定义,其中嵌入了逻辑、组件类 ID、方法、注册逻辑等。

还有一个tool available to help in creating a WSC。基本上它是一个向导类型的东西,它会问你问题并填写 XML 模板。我自己,我刚开始使用示例 .wsc 文件并使用文本编辑器手动编辑它。这是不言自明的。

以这种方式在脚本中定义的 COM 组件(在 .wsc 文件中)可以像任何其他 COM 组件一样从任何可以与 COM 共舞的环境中调用。

更新:我花了几分钟时间为 GoogleDiff 制作了 WSC。在这里。

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

要使用那个东西,你必须注册它。在资源管理器中,右键单击它,然后选择“注册”。或者,从命令行: regsvr32 文件:\c:\scripts\GoogleDiff.wsc

我没有尝试从 VBA 中使用它,但这里有一些使用该组件的 VBScript 代码。

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

【讨论】:

  • 太棒了。当我有机会时,我会试一试。同时,我会接受这个作为最佳答案。
  • 关闭,但 diff_main 返回一个差异数组,每个差异数组都是一个包含运算符(等于、删除或插入,作为整数)和文本的二元素数组。我仍在研究如何让 VBA 将结果视为一个数组,以便我可以逐步完成它并在 Excel 单元格中创建适当的格式。
  • 我知道javascript逻辑认为返回值是什么。在我的VBScript 测试中,返回值的类型是String。因此,在我的示例 vbscript 中,我拆分了字符串并重新构建了“差异”数组。
【解决方案2】:

Windows Scripting Engine 将允许您运行 JavaScript 库。根据我的经验,它运作良好。

【讨论】:

  • 并且通过将Javascript逻辑打包成COM组件,通过微软称之为Windows Script Components的这个东西,从Excel/VBA调用Javascript就很容易了。
【解决方案3】:

我的建议是,无论您做什么,都将其包装在 COM 包装器中。 VBA 最好地处理 COM 对象,因此您可以编译为 .NET 组件,然后使用 .NET 的互操作功能公开为 COM 对象。

作为替代方案,您还可以考虑使用 Windows Scripting Host 对象来执行 Javascript 文件并返回结果。

【讨论】:

  • 你可以两者都做。使用 Windows 脚本组件,您可以在 Javascript 中定义您的 COM 组件,并从 VBA 或其他方式调用 COM 组件。
【解决方案4】:

这是另一种可供考虑的选择,尽管我并没有说它是最好的选择。

  • 确保 Python 版本在 IronPython 中编译。 (这里应该没有问题,或者最多只是少量的移植。)
  • 使用 C# 创建一个 Excel 插件库并从中引用 IronPython。
  • 在您的 C# Excel 插件中封装必要的功能。

【讨论】:

  • 这将获得 all .Net 解决方案。我喜欢它。
  • 我想要一个全 .NET 解决方案,但我坚持使用 Excel 2003。此外,我的用户可能安装了也可能没有安装特定版本的 .NET 运行时,所以一个全-首选 VBA 解决方案。
  • 在这种情况下(基于 COM 的)Windows 脚本引擎应该符合要求。
  • 后续:根据FAQ,IronPython不支持编译成.NET库。倾向于使用 WSE。
  • 是的,我建议这样做主要是因为它不需要可怕的 VBA 并且纯粹坚持使用 .NET。请注意,IronPython 代码可以在 C# 应用程序中运行。这需要一些研究(我以前没有自己尝试过),但我很确定这是可能的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-19
  • 1970-01-01
  • 2015-05-07
  • 2013-12-08
  • 1970-01-01
  • 2012-11-28
相关资源
最近更新 更多