8

为了扩展 OpenOffice 的功能,我在用户脚本目录(在我的例子中是 ~/Library/Application Support/OpenOffice.org/3/user/Scripts/python/)的一个文件中定义了一些 Python 宏。宏在 Python 宏管理器中可见。但是,在单元格公式中使用函数会导致“#NAME?” (OO.org 错误525)。

假设我定义了以下函数:

def pytype(val):
    return str(type(val))

如何调用pytype单元格公式(例如=PYTYPE("string"))?

背景

我正在将 Authorize.net 中的一些数据导入 MySQL 数据库进行分析。MySQL 无法将 Authorize.net 使用的日期和时间格式解析为DATETIMEorTIMESTAMP字段,因此我试图将数据按摩成 MySQL 在导入之前可以处理的格式。OpenOffice 也不会将数据识别为日期和时间,而且据我所知,OO.Org 没有通用的日期解析功能。因此,我正在扩展 OO.org 的功能。

对于更大的问题还有其他方法。例如,我还可以尝试使用附加列在导入后修复 MySQL 中的数据。事实上,这是我第一次做的;但是,现在表中有现有数据需要处理。正因为如此,并且因为将来我希望通过在公式中使用宏来完成其他任务,所以现在我最感兴趣的是在公式中调用 Python 宏。

4

2 回答 2

5

在旧的 OO.org 论坛上,(超级)用户 Villeroy 发布了一个说明如何从 OO.org Basic 调用 Python 函数,然后可以在公式中使用它。关键是将com.sun.star.script.provider.MasterScriptProviderFactory服务用作桥梁。这是他的解决方案的改编版,泛化为在任意模块中调用任意函数:

REM Keep a global reference to the ScriptProvider, since this stuff may be called many times: 
Global g_MasterScriptProvider as Object
REM Specify location of Python script, providing cell functions: 
Const URL_Main as String = "vnd.sun.star.script:" 
Const URL_Args as String = "?language=Python&location=user" 

Function invokePyFunc(file AS String, func As String, args As Array, outIdxs As Array, outArgs As Array)
   sURL = URL_Main & file & ".py$" & func & URL_Args
   oMSP = getMasterScriptProvider()
   On Local Error GoTo ErrorHandler
      oScript = oMSP.getScript(sURL)
      invokePyFunc = oScript.invoke(args, outIdxs, outArgs)
      Exit Function
   ErrorHandler:
      Dim msg As String, toFix As String
      msg = Error$
      toFix = ""
      If 1 = Err AND InStr(Error$, "an error occurred during file opening") Then
         msg = "Couldn' open the script file."
         toFix = "Make sure the 'python' folder exists in the user's Scripts folder, and that the former contains " & file & ".py."
      End If
      MsgBox msg & chr(13) & toFix, 16, "Error " & Err & " calling " & func
end Function

Function getMasterScriptProvider() 
   if isNull(g_MasterScriptProvider) then 
      oMasterScriptProviderFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory") 
      g_MasterScriptProvider = oMasterScriptProviderFactory.createScriptProvider("") 
   endif 
   getMasterScriptProvider = g_MasterScriptProvider
End Function

然后可以使用它来创建可在公式中调用的 OO.org Basic 函数。使用示例pytype

Const libfile as String = "util"    REM functions live in util.py

Function pytype(value)
    pytype = invokePyFunc(libfile, "pytype", Array(value), Array(), Array())
End Function

另一个可能的实现是创建一个Python 插件。然而,这是一个更重的选项,因为它需要安装 OpenOffice SDK,而且我不清楚这种方法是否适用于免费功能或仅适用于类。

于 2011-09-29T05:19:03.600 回答
3

outis - 感谢您的精彩回答。如果不是你,我现在还在写基本的宏!

不过,我只是有一些评论:

invokePyFunc 的最后 2 个参数始终为空 - 只需使用以下命令:

const filename = "your_file"

Function pyFunc(func as String, args as Array)
    pyFunc = invokePyFunc(filename, func, args, Array(), Array())
End Function

多维数组很难返回。如果您返回((1,2,3), (4,5,6))calc 将其视为包含未知对象的一行中的 2 个单元格。

这是因为 basic 和 python 对待多维数组的方式不同。

如果将这种结构返回到基本结构,则必须像data(row)(col)calcdata(row, col)对多维数组的期望一样访问它。

因此,您需要对返回值使用转换器函数:

' Converts python multidimensional arrays to basic arrays.
function convPy2Basic(pyvalue)
    if isarray(pyvalue) then
        dim lastRow as integer 
        lastRow = ubound(pyvalue)
        if lastRow = -1 then
            ' empty array
            convPy2Basic = ""
        else
            if isarray(pyvalue(0)) then
                ' Multi-dimensional array
                dim maxCols as integer, lastCol as integer
                maxCols = ubound(pyvalue(0))

                dim res(lastRow, maxCols)
                for rowIndex = 0 to lastRow
                    lastCol = ubound(pyvalue(rowIndex))

                    ' Expand array if needed.
                    if lastCol > maxCols then
                        maxCols = lastCol
                        redim preserve res(lastRow, maxCols)
                    end if

                    for colIndex = 0 to lastCol
                        res(rowIndex, colIndex) = pyvalue(rowIndex)(colIndex)
                    next colIndex
                next rowIndex

                convPy2Basic = res
            else
                ' Single-dimensional array - this is supported by libreoffice
                convPy2Basic = pyvalue
            end if
        end if
    else
        convPy2Basic = pyvalue
    end if
end function

Function invokeScriptFunc(file AS String, lang, ext, func As String, args As Array, outIdxs As Array, outArgs As Array)
   sURL = URL_Main & file & "." & ext & "$" & func & "?language=" & lang & "&location=user" 
   oMSP = getMasterScriptProvider()
   oScript = oMSP.getScript(sURL)
   invokeScriptFunc = oScript.invoke(args, outIdxs, outArgs)
end Function

Function invokePyFunc(file AS String, func As String, args As Array, outIdxs As Array, outArgs As Array)
   res = invokeScriptFunc(file, "Python", "py", func, args, outIdxs, outArgs)
   invokePyFunc = convPy2Basic(res)
end Function

所以我的 python-basic 宏桥看起来像这样:

' Keep a global reference to the ScriptProvider, since this stuff may be called many times: 
Global g_MasterScriptProvider as Object
' Specify location of Python script, providing cell functions: 
Const URL_Main as String = "vnd.sun.star.script:"

' Converts python multidimensional arrays to basic arrays.
function convPy2Basic(pyvalue)
    if isarray(pyvalue) then
        dim lastRow as integer 
        lastRow = ubound(pyvalue)
        if lastRow = -1 then
            ' empty array
            convPy2Basic = ""
        else
            if isarray(pyvalue(0)) then
                ' Multi-dimensional array
                dim maxCols as integer, lastCol as integer
                maxCols = ubound(pyvalue(0))

                dim res(lastRow, maxCols)
                for rowIndex = 0 to lastRow
                    lastCol = ubound(pyvalue(rowIndex))

                    ' Expand array if needed.
                    if lastCol > maxCols then
                        maxCols = lastCol
                        redim preserve res(lastRow, maxCols)
                    end if

                    for colIndex = 0 to lastCol
                        res(rowIndex, colIndex) = pyvalue(rowIndex)(colIndex)
                    next colIndex
                next rowIndex

                convPy2Basic = res
            else
                ' Single-dimensional array - this is supported by libreoffice
                convPy2Basic = pyvalue
            end if
        end if
    else
        convPy2Basic = pyvalue
    end if
end function

Function invokeScriptFunc(file AS String, lang, ext, func As String, args As Array, outIdxs As Array, outArgs As Array)
   sURL = URL_Main & file & "." & ext & "$" & func & "?language=" & lang & "&location=user" 
   oMSP = getMasterScriptProvider()
   oScript = oMSP.getScript(sURL)
   invokeScriptFunc = oScript.invoke(args, outIdxs, outArgs)
end Function

Function invokePyFunc(file AS String, func As String, args As Array, outIdxs As Array, outArgs As Array)
   res = invokeScriptFunc(file, "Python", "py", func, args, outIdxs, outArgs)
   invokePyFunc = convPy2Basic(res)
end Function

Function getMasterScriptProvider() 
   if isNull(g_MasterScriptProvider) then 
      oMasterScriptProviderFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory") 
      g_MasterScriptProvider = oMasterScriptProviderFactory.createScriptProvider("") 
   endif 
   getMasterScriptProvider = g_MasterScriptProvider
End Function

const filename = "skaiciuokle"

Function pyFunc(func as String, args as Array)
    pyFunc = invokePyFunc(filename, func, args, Array(), Array())
End Function

并且是这样使用的:

function DamageToArmor(data, damageType as String, armorType as String, dmgPerGun as Integer, guns as Integer)
    DamageToArmor = pyFunc("dmg2armor", Array(data, damageType, armorType, dmgPerGun, guns))
end function
于 2012-01-23T20:56:39.970 回答