Repository: cunnane/xloil Branch: master Commit: 21c0f866351d Files: 680 Total size: 4.6 MB Directory structure: gitextract__p1idbhy/ ├── .gitattributes ├── .gitignore ├── .readthedocs.yml ├── .runsettings ├── FUNDING.yml ├── LICENSE ├── README.md ├── Version.txt ├── config/ │ ├── xloil.ini │ ├── xloil_local.ini │ └── xloil_local_32.ini ├── docs/ │ ├── Excel Function Numbers.xlsx │ ├── make.cmd │ ├── requirements.txt │ ├── source/ │ │ ├── Concepts.rst │ │ ├── Core.rst │ │ ├── Developer.rst │ │ ├── Events.rst │ │ ├── Introduction.rst │ │ ├── conf.py │ │ ├── index.rst │ │ ├── xlOil_Core_Functions.rst │ │ ├── xlOil_Cpp/ │ │ │ ├── COM.rst │ │ │ ├── CppRtd.rst │ │ │ ├── CustomGUI.rst │ │ │ ├── DynamicRegistration.rst │ │ │ ├── Events.rst │ │ │ ├── GettingStarted.rst │ │ │ ├── ObjectHandles.rst │ │ │ ├── SpecialArgs.rst │ │ │ ├── StaticXLLs.rst │ │ │ └── index.rst │ │ ├── xlOil_Python/ │ │ │ ├── BuiltInUDFs.rst │ │ │ ├── Concepts.rst │ │ │ ├── CustomGUI.rst │ │ │ ├── Debugging.rst │ │ │ ├── DistributingAddins.rst │ │ │ ├── Dynamic.rst │ │ │ ├── Example.rst │ │ │ ├── ExampleGUI.rst │ │ │ ├── ExampleRTD.rst │ │ │ ├── ExcelApplication.rst │ │ │ ├── ExternalPackages.rst │ │ │ ├── FAQ.rst │ │ │ ├── Functions.rst │ │ │ ├── GettingStarted.rst │ │ │ ├── Jupyter.rst │ │ │ ├── ModuleReference.rst │ │ │ ├── Rtd.rst │ │ │ ├── TypeConversion.rst │ │ │ └── index.rst │ │ ├── xlOil_SQL/ │ │ │ └── index.rst │ │ ├── xlOil_Utils.rst │ │ └── xloil.doxyfile │ └── xlOil-Sphinx.code-workspace ├── external/ │ ├── Excel2013SDK/ │ │ ├── INCLUDE/ │ │ │ └── XLCALL.H │ │ ├── LIB/ │ │ │ ├── WIn32/ │ │ │ │ └── XLCALL32.LIB │ │ │ └── x64/ │ │ │ └── XLCALL32.LIB │ │ └── SRC/ │ │ ├── ReadMe.txt │ │ └── XLCALL.CPP │ ├── Sources.txt │ └── boost-1.67/ │ └── boost/ │ └── preprocessor/ │ ├── arithmetic/ │ │ ├── add.hpp │ │ ├── dec.hpp │ │ ├── detail/ │ │ │ └── div_base.hpp │ │ ├── div.hpp │ │ ├── inc.hpp │ │ ├── mod.hpp │ │ ├── mul.hpp │ │ └── sub.hpp │ ├── arithmetic.hpp │ ├── array/ │ │ ├── data.hpp │ │ ├── detail/ │ │ │ └── get_data.hpp │ │ ├── elem.hpp │ │ ├── enum.hpp │ │ ├── insert.hpp │ │ ├── pop_back.hpp │ │ ├── pop_front.hpp │ │ ├── push_back.hpp │ │ ├── push_front.hpp │ │ ├── remove.hpp │ │ ├── replace.hpp │ │ ├── reverse.hpp │ │ ├── size.hpp │ │ ├── to_list.hpp │ │ ├── to_seq.hpp │ │ └── to_tuple.hpp │ ├── array.hpp │ ├── assert_msg.hpp │ ├── cat.hpp │ ├── comma.hpp │ ├── comma_if.hpp │ ├── comparison/ │ │ ├── equal.hpp │ │ ├── greater.hpp │ │ ├── greater_equal.hpp │ │ ├── less.hpp │ │ ├── less_equal.hpp │ │ └── not_equal.hpp │ ├── comparison.hpp │ ├── config/ │ │ ├── config.hpp │ │ └── limits.hpp │ ├── control/ │ │ ├── deduce_d.hpp │ │ ├── detail/ │ │ │ ├── dmc/ │ │ │ │ └── while.hpp │ │ │ ├── edg/ │ │ │ │ └── while.hpp │ │ │ ├── msvc/ │ │ │ │ └── while.hpp │ │ │ └── while.hpp │ │ ├── expr_if.hpp │ │ ├── expr_iif.hpp │ │ ├── if.hpp │ │ ├── iif.hpp │ │ └── while.hpp │ ├── control.hpp │ ├── debug/ │ │ ├── assert.hpp │ │ ├── error.hpp │ │ └── line.hpp │ ├── debug.hpp │ ├── dec.hpp │ ├── detail/ │ │ ├── auto_rec.hpp │ │ ├── check.hpp │ │ ├── dmc/ │ │ │ └── auto_rec.hpp │ │ ├── is_binary.hpp │ │ ├── is_nullary.hpp │ │ ├── is_unary.hpp │ │ ├── null.hpp │ │ └── split.hpp │ ├── empty.hpp │ ├── enum.hpp │ ├── enum_params.hpp │ ├── enum_params_with_a_default.hpp │ ├── enum_params_with_defaults.hpp │ ├── enum_shifted.hpp │ ├── enum_shifted_params.hpp │ ├── expand.hpp │ ├── expr_if.hpp │ ├── facilities/ │ │ ├── apply.hpp │ │ ├── detail/ │ │ │ └── is_empty.hpp │ │ ├── empty.hpp │ │ ├── expand.hpp │ │ ├── identity.hpp │ │ ├── intercept.hpp │ │ ├── is_1.hpp │ │ ├── is_empty.hpp │ │ ├── is_empty_or_1.hpp │ │ ├── is_empty_variadic.hpp │ │ └── overload.hpp │ ├── facilities.hpp │ ├── for.hpp │ ├── identity.hpp │ ├── if.hpp │ ├── inc.hpp │ ├── iterate.hpp │ ├── iteration/ │ │ ├── detail/ │ │ │ ├── bounds/ │ │ │ │ ├── lower1.hpp │ │ │ │ ├── lower2.hpp │ │ │ │ ├── lower3.hpp │ │ │ │ ├── lower4.hpp │ │ │ │ ├── lower5.hpp │ │ │ │ ├── upper1.hpp │ │ │ │ ├── upper2.hpp │ │ │ │ ├── upper3.hpp │ │ │ │ ├── upper4.hpp │ │ │ │ └── upper5.hpp │ │ │ ├── finish.hpp │ │ │ ├── iter/ │ │ │ │ ├── forward1.hpp │ │ │ │ ├── forward2.hpp │ │ │ │ ├── forward3.hpp │ │ │ │ ├── forward4.hpp │ │ │ │ ├── forward5.hpp │ │ │ │ ├── reverse1.hpp │ │ │ │ ├── reverse2.hpp │ │ │ │ ├── reverse3.hpp │ │ │ │ ├── reverse4.hpp │ │ │ │ └── reverse5.hpp │ │ │ ├── local.hpp │ │ │ ├── rlocal.hpp │ │ │ ├── self.hpp │ │ │ └── start.hpp │ │ ├── iterate.hpp │ │ ├── local.hpp │ │ └── self.hpp │ ├── iteration.hpp │ ├── library.hpp │ ├── limits.hpp │ ├── list/ │ │ ├── adt.hpp │ │ ├── append.hpp │ │ ├── at.hpp │ │ ├── cat.hpp │ │ ├── detail/ │ │ │ ├── dmc/ │ │ │ │ └── fold_left.hpp │ │ │ ├── edg/ │ │ │ │ ├── fold_left.hpp │ │ │ │ └── fold_right.hpp │ │ │ ├── fold_left.hpp │ │ │ └── fold_right.hpp │ │ ├── enum.hpp │ │ ├── filter.hpp │ │ ├── first_n.hpp │ │ ├── fold_left.hpp │ │ ├── fold_right.hpp │ │ ├── for_each.hpp │ │ ├── for_each_i.hpp │ │ ├── for_each_product.hpp │ │ ├── rest_n.hpp │ │ ├── reverse.hpp │ │ ├── size.hpp │ │ ├── to_array.hpp │ │ ├── to_seq.hpp │ │ ├── to_tuple.hpp │ │ └── transform.hpp │ ├── list.hpp │ ├── logical/ │ │ ├── and.hpp │ │ ├── bitand.hpp │ │ ├── bitnor.hpp │ │ ├── bitor.hpp │ │ ├── bitxor.hpp │ │ ├── bool.hpp │ │ ├── compl.hpp │ │ ├── nor.hpp │ │ ├── not.hpp │ │ ├── or.hpp │ │ └── xor.hpp │ ├── logical.hpp │ ├── max.hpp │ ├── min.hpp │ ├── punctuation/ │ │ ├── comma.hpp │ │ ├── comma_if.hpp │ │ ├── detail/ │ │ │ └── is_begin_parens.hpp │ │ ├── is_begin_parens.hpp │ │ ├── paren.hpp │ │ ├── paren_if.hpp │ │ └── remove_parens.hpp │ ├── punctuation.hpp │ ├── repeat.hpp │ ├── repeat_2nd.hpp │ ├── repeat_3rd.hpp │ ├── repeat_from_to.hpp │ ├── repeat_from_to_2nd.hpp │ ├── repeat_from_to_3rd.hpp │ ├── repetition/ │ │ ├── deduce_r.hpp │ │ ├── deduce_z.hpp │ │ ├── detail/ │ │ │ ├── dmc/ │ │ │ │ └── for.hpp │ │ │ ├── edg/ │ │ │ │ └── for.hpp │ │ │ ├── for.hpp │ │ │ └── msvc/ │ │ │ └── for.hpp │ │ ├── enum.hpp │ │ ├── enum_binary_params.hpp │ │ ├── enum_params.hpp │ │ ├── enum_params_with_a_default.hpp │ │ ├── enum_params_with_defaults.hpp │ │ ├── enum_shifted.hpp │ │ ├── enum_shifted_binary_params.hpp │ │ ├── enum_shifted_params.hpp │ │ ├── enum_trailing.hpp │ │ ├── enum_trailing_binary_params.hpp │ │ ├── enum_trailing_params.hpp │ │ ├── for.hpp │ │ ├── repeat.hpp │ │ └── repeat_from_to.hpp │ ├── repetition.hpp │ ├── selection/ │ │ ├── max.hpp │ │ └── min.hpp │ ├── selection.hpp │ ├── seq/ │ │ ├── cat.hpp │ │ ├── detail/ │ │ │ ├── binary_transform.hpp │ │ │ ├── is_empty.hpp │ │ │ ├── split.hpp │ │ │ └── to_list_msvc.hpp │ │ ├── elem.hpp │ │ ├── enum.hpp │ │ ├── filter.hpp │ │ ├── first_n.hpp │ │ ├── fold_left.hpp │ │ ├── fold_right.hpp │ │ ├── for_each.hpp │ │ ├── for_each_i.hpp │ │ ├── for_each_product.hpp │ │ ├── insert.hpp │ │ ├── pop_back.hpp │ │ ├── pop_front.hpp │ │ ├── push_back.hpp │ │ ├── push_front.hpp │ │ ├── remove.hpp │ │ ├── replace.hpp │ │ ├── rest_n.hpp │ │ ├── reverse.hpp │ │ ├── seq.hpp │ │ ├── size.hpp │ │ ├── subseq.hpp │ │ ├── to_array.hpp │ │ ├── to_list.hpp │ │ ├── to_tuple.hpp │ │ ├── transform.hpp │ │ └── variadic_seq_to_seq.hpp │ ├── seq.hpp │ ├── slot/ │ │ ├── counter.hpp │ │ ├── detail/ │ │ │ ├── counter.hpp │ │ │ ├── def.hpp │ │ │ ├── shared.hpp │ │ │ ├── slot1.hpp │ │ │ ├── slot2.hpp │ │ │ ├── slot3.hpp │ │ │ ├── slot4.hpp │ │ │ └── slot5.hpp │ │ └── slot.hpp │ ├── slot.hpp │ ├── stringize.hpp │ ├── tuple/ │ │ ├── detail/ │ │ │ └── is_single_return.hpp │ │ ├── eat.hpp │ │ ├── elem.hpp │ │ ├── enum.hpp │ │ ├── insert.hpp │ │ ├── pop_back.hpp │ │ ├── pop_front.hpp │ │ ├── push_back.hpp │ │ ├── push_front.hpp │ │ ├── rem.hpp │ │ ├── remove.hpp │ │ ├── replace.hpp │ │ ├── reverse.hpp │ │ ├── size.hpp │ │ ├── to_array.hpp │ │ ├── to_list.hpp │ │ └── to_seq.hpp │ ├── tuple.hpp │ ├── variadic/ │ │ ├── detail/ │ │ │ └── is_single_return.hpp │ │ ├── elem.hpp │ │ ├── size.hpp │ │ ├── to_array.hpp │ │ ├── to_list.hpp │ │ ├── to_seq.hpp │ │ └── to_tuple.hpp │ ├── variadic.hpp │ ├── while.hpp │ └── wstringize.hpp ├── include/ │ └── xloil/ │ ├── AppObjects.h │ ├── ArrayBuilder.h │ ├── Async.h │ ├── AutoBind.h │ ├── Caller.h │ ├── Date.h │ ├── DynamicRegister.h │ ├── EnumHelper.h │ ├── Events.h │ ├── ExcelArray.h │ ├── ExcelCall.h │ ├── ExcelObj.h │ ├── ExcelObjCache.h │ ├── ExcelRef.h │ ├── ExcelThread.h │ ├── ExcelTypeLib.h │ ├── ExcelUI.h │ ├── ExportMacro.h │ ├── FPArray.h │ ├── FuncSpec.h │ ├── Interface.h │ ├── Limits.h │ ├── Log.h │ ├── LogWindow.h │ ├── NumericTypeConverters.h │ ├── ObjectCache.h │ ├── PString.h │ ├── Plugin.h │ ├── Preprocessor.h │ ├── Range.h │ ├── Register.h │ ├── RtdServer.h │ ├── State.h │ ├── StaticRegister.h │ ├── StringUtils.h │ ├── Throw.h │ ├── TypeConverters.h │ ├── Version.h │ ├── WindowsSlim.h │ ├── XlCallSlim.h │ ├── XllEntryPoint.h │ └── xlOil.h ├── libs/ │ ├── SetRoot.props │ ├── xlOilPlugin.props │ ├── xlOilStaticLib.props │ ├── xlOil_Python/ │ │ ├── ArrayHelpers.h │ │ ├── AsyncFunctions.cpp │ │ ├── AsyncFunctions.h │ │ ├── CPython.h │ │ ├── EventLoop.cpp │ │ ├── EventLoop.h │ │ ├── Loader/ │ │ │ ├── StubLoader.cpp │ │ │ └── xlOil_Python.vcxproj │ │ ├── Main.cpp │ │ ├── Package/ │ │ │ ├── TestConfig.py │ │ │ ├── generate_stubs.py │ │ │ ├── setup.py │ │ │ ├── test_JupyterConnection.py │ │ │ ├── test_PythonAutomation.py │ │ │ ├── test_SpreadsheetRunner.py │ │ │ ├── xlOil_Python_Package.pyproj │ │ │ └── xloil/ │ │ │ ├── __init__.py │ │ │ ├── _core.py │ │ │ ├── _event_loop.py │ │ │ ├── _paths.py │ │ │ ├── _pyinstaller/ │ │ │ │ ├── __init__.py │ │ │ │ └── hook-xloil.py │ │ │ ├── _superreload.py │ │ │ ├── com.py │ │ │ ├── command_line.py │ │ │ ├── debug.py │ │ │ ├── excelfuncs.py │ │ │ ├── func_inspect.py │ │ │ ├── gui/ │ │ │ │ ├── __init__.py │ │ │ │ ├── qt_console.py │ │ │ │ ├── qtpy.py │ │ │ │ ├── tk_console.py │ │ │ │ ├── tkinter.py │ │ │ │ └── wx.py │ │ │ ├── importer.py │ │ │ ├── jupyter.py │ │ │ ├── jupyter_kernel.py │ │ │ ├── logging.py │ │ │ ├── matplotlib.py │ │ │ ├── pandas.py │ │ │ ├── pillow.py │ │ │ ├── register.py │ │ │ ├── rtd.py │ │ │ ├── stubs/ │ │ │ │ ├── __init__.py │ │ │ │ └── xloil_core/ │ │ │ │ ├── __init__.py │ │ │ │ └── event/ │ │ │ │ └── __init__.py │ │ │ ├── type_converters.py │ │ │ └── xloil_ribbon.py │ │ ├── PyAddin.cpp │ │ ├── PyAddin.h │ │ ├── PyAddress.cpp │ │ ├── PyAddress.h │ │ ├── PyAppCallRun.cpp │ │ ├── PyAppCallRun.h │ │ ├── PyAppObjects.cpp │ │ ├── PyCOM.cpp │ │ ├── PyCOM.h │ │ ├── PyCache.cpp │ │ ├── PyCache.h │ │ ├── PyCore.cpp │ │ ├── PyCore.h │ │ ├── PyEvents.cpp │ │ ├── PyEvents.h │ │ ├── PyFunctionRegister.cpp │ │ ├── PyFunctionRegister.h │ │ ├── PyFuture.h │ │ ├── PyHelpers.cpp │ │ ├── PyHelpers.h │ │ ├── PyImage.cpp │ │ ├── PyImage.h │ │ ├── PyLogWriter.cpp │ │ ├── PyObjectGC.cpp │ │ ├── PyRibbon.cpp │ │ ├── PyRtd.cpp │ │ ├── PyRtd.h │ │ ├── PyRunLater.cpp │ │ ├── PySettings.props │ │ ├── PySettings310.props │ │ ├── PySettings311.props │ │ ├── PySettings312.props │ │ ├── PySettings313.props │ │ ├── PySettings314.props │ │ ├── PySettings36.props │ │ ├── PySettings37.props │ │ ├── PySettings38.props │ │ ├── PySettings39.props │ │ ├── PySource.cpp │ │ ├── PySource.h │ │ ├── PyStatusBar.cpp │ │ ├── TypeConversion/ │ │ │ ├── BasicTypes.cpp │ │ │ ├── BasicTypes.h │ │ │ ├── ConverterInterface.h │ │ │ ├── Numpy.h │ │ │ ├── NumpyDatetime.cpp │ │ │ ├── NumpyDatetime.h │ │ │ ├── NumpyFromExcel.cpp │ │ │ ├── NumpyHelpers.cpp │ │ │ ├── NumpyHelpers.h │ │ │ ├── NumpyPandas.cpp │ │ │ ├── NumpyToExcel.cpp │ │ │ ├── PyCustomType.cpp │ │ │ ├── PyDateType.cpp │ │ │ ├── PyDateType.h │ │ │ ├── PyDictType.cpp │ │ │ ├── PyDictType.h │ │ │ ├── PyExcelArrayType.cpp │ │ │ ├── PyExcelArrayType.h │ │ │ ├── PyRangeType.cpp │ │ │ ├── PyTupleType.cpp │ │ │ └── PyTupleType.h │ │ ├── xlOil_Python310.vcxproj │ │ ├── xlOil_Python311.vcxproj │ │ ├── xlOil_Python312.vcxproj │ │ ├── xlOil_Python313.vcxproj │ │ ├── xlOil_Python314.vcxproj │ │ ├── xlOil_Python36.vcxproj │ │ ├── xlOil_Python37.vcxproj │ │ ├── xlOil_Python38.vcxproj │ │ ├── xlOil_Python39.vcxproj │ │ └── xlOil_Python39.vcxproj.filters │ ├── xlOil_SQL/ │ │ ├── Cache.cpp │ │ ├── Cache.h │ │ ├── Common.cpp │ │ ├── Common.h │ │ ├── Main.cpp │ │ ├── XlArrayTable.cpp │ │ ├── XlArrayTable.h │ │ ├── xlOil_SQL.vcxproj │ │ ├── xlOil_SQL.vcxproj.filters │ │ ├── xloSql.cpp │ │ ├── xloSqlDB.cpp │ │ ├── xloSqlQuery.cpp │ │ ├── xloSqlTable.cpp │ │ └── xloSqlTables.cpp │ └── xlOil_Utils/ │ ├── Main.cpp │ ├── RegexHelpers.h │ ├── xlOil_Utils.vcxproj │ ├── xloBlock.cpp │ ├── xloConcat.cpp │ ├── xloFill.cpp │ ├── xloFillNA.cpp │ ├── xloIndex.cpp │ ├── xloPad.cpp │ ├── xloRegex.cpp │ ├── xloSearch.cpp │ ├── xloSort.cpp │ └── xloSplit.cpp ├── src/ │ ├── BuildPaths.props │ ├── Common.props │ ├── Debug.props │ ├── Release.props │ ├── external/ │ │ ├── asmjit.vcxproj │ │ ├── rdcfswatcher.vcxproj │ │ └── spdlog.vcxproj │ ├── xlOil/ │ │ ├── Interface.cpp │ │ ├── Loaders/ │ │ │ ├── AddinLoader.cpp │ │ │ ├── AddinLoader.h │ │ │ ├── CoreEntryPoint.cpp │ │ │ ├── CoreEntryPoint.h │ │ │ ├── PluginLoader.cpp │ │ │ └── PluginLoader.h │ │ ├── Log.cpp │ │ ├── xlOil.vcxproj │ │ └── xlOil.vcxproj.filters │ ├── xlOil-COM/ │ │ ├── API/ │ │ │ ├── ComUtils.cpp │ │ │ ├── Events.cpp │ │ │ ├── ExcelRange.cpp │ │ │ ├── ExcelThread.cpp │ │ │ └── RtdServer.cpp │ │ ├── AppObjects.cpp │ │ ├── ClassFactory.cpp │ │ ├── ClassFactory.h │ │ ├── ComAddin.cpp │ │ ├── ComAddin.h │ │ ├── ComEventSink.cpp │ │ ├── ComEventSink.h │ │ ├── ComVariant.cpp │ │ ├── ComVariant.h │ │ ├── Connect.cpp │ │ ├── Connect.h │ │ ├── CustomTaskPane.cpp │ │ ├── CustomTaskPane.h │ │ ├── RibbonExtensibility.cpp │ │ ├── RibbonExtensibility.h │ │ ├── RtdAsyncManager.cpp │ │ ├── RtdAsyncManager.h │ │ ├── RtdManager.cpp │ │ ├── RtdManager.h │ │ ├── RtdServerWorker.h │ │ ├── TaskPaneHostControl.cpp │ │ ├── TaskPaneHostControl.h │ │ ├── WorkbookScopeFunctions.cpp │ │ ├── WorkbookScopeFunctions.h │ │ ├── XllContextInvoke.cpp │ │ ├── XllContextInvoke.h │ │ ├── xlOil-COM.vcxproj │ │ └── xlOil-COM.vcxproj.filters │ ├── xlOil-Dynamic/ │ │ ├── DynamicRegistration.cpp │ │ ├── ExternalRegionAllocator.h │ │ ├── LocalFunctions.cpp │ │ ├── LocalFunctions.h │ │ ├── PEHelper.cpp │ │ ├── PEHelper.h │ │ ├── Thunker.cpp │ │ ├── Thunker.h │ │ ├── xlOil-Dynamic.vcxproj │ │ └── xlOil-Dynamic.vcxproj.filters │ ├── xlOil-Funcs/ │ │ ├── ExcelObjCache.cpp │ │ ├── xlOil-Funcs.vcxproj │ │ ├── xlOil-Funcs.vcxproj.filters │ │ ├── xloHelp.cpp │ │ ├── xloLog.cpp │ │ ├── xloReload.cpp │ │ └── xloVersion.cpp │ ├── xlOil-Loader/ │ │ ├── LoaderEntryPoint.cpp │ │ ├── xlOil-Loader.vcxproj │ │ └── xlOilAddin.def │ ├── xlOil-XLL/ │ │ ├── ArrayBuilder.cpp │ │ ├── Async.cpp │ │ ├── Caller.cpp │ │ ├── Date.cpp │ │ ├── ExcelArray.cpp │ │ ├── ExcelCall.cpp │ │ ├── ExcelCallMapping.h │ │ ├── ExcelObj.cpp │ │ ├── ExcelRef.cpp │ │ ├── FPArray.cpp │ │ ├── FuncRegistry.cpp │ │ ├── FuncRegistry.h │ │ ├── Intellisense.cpp │ │ ├── Intellisense.h │ │ ├── Log.cpp │ │ ├── LogWindow.cpp │ │ ├── LogWindowSink.cpp │ │ ├── LogWindowSink.h │ │ ├── State.cpp │ │ ├── StaticRegister.cpp │ │ ├── Throw.cpp │ │ ├── XlCall.cpp │ │ ├── XllEvents.cpp │ │ ├── xlOil-XLL.vcxproj │ │ └── xlOil-XLL.vcxproj.filters │ └── xlOilHelpers/ │ ├── Environment.cpp │ ├── Environment.h │ ├── Exception.h │ ├── GuidUtils.cpp │ ├── GuidUtils.h │ ├── Settings.cpp │ ├── Settings.h │ ├── Utils.h │ └── xlOilHelpers.vcxproj ├── tests/ │ ├── AutoSheets/ │ │ ├── PythonTest.py │ │ ├── PythonTest.xlsx │ │ ├── TestModule.py │ │ ├── TestSQL.xlsx │ │ ├── TestUtils.ipynb │ │ └── TestUtils.xlsx │ ├── CodePageConversion.cpp │ ├── Date.cpp │ ├── Environment.cpp │ ├── ManualSheets/ │ │ ├── python/ │ │ │ ├── PySpeedTest.py │ │ │ ├── PySpeedTest.xlsm │ │ │ ├── PythonRTDTest.py │ │ │ ├── PythonRTDTest.xlsx │ │ │ ├── PythonTestAsync.py │ │ │ ├── PythonTestAsync.xlsm │ │ │ ├── PythonTestUI.py │ │ │ ├── PythonTestUI.xlsm │ │ │ ├── TestJupyter.xlsx │ │ │ └── TestJupyterConnection.ipynb │ │ └── rtd/ │ │ └── RtdTest.xlsx │ ├── PString.cpp │ ├── TestAddin/ │ │ ├── AutoBind.cpp │ │ ├── CacheTest.cpp │ │ ├── CacheTest2.cpp │ │ ├── CallbackTest.cpp │ │ ├── ExoticArgTest.cpp │ │ ├── Main.cpp │ │ ├── RtdServerTest.cpp │ │ ├── TestAddin.vcxproj │ │ ├── TestAddin.vcxproj.filters │ │ └── TestTestAddin.xlsx │ ├── TestArrayBuilder.cpp │ ├── TestCOM.cpp │ ├── TestCache.cpp │ ├── TestExcelCall.cpp │ ├── TestExcelObj.cpp │ ├── TestGuid.cpp │ ├── TestRange.cpp │ ├── TestSimpleAllocator.cpp │ ├── TestStringUtils.cpp │ ├── TestTempFile.cpp │ ├── TestThunker.cpp │ ├── Tests.vcxproj │ └── Tests.vcxproj.filters ├── tools/ │ ├── BuildRelease.cmd │ ├── DownloadDependencies.cmd │ ├── WriteInclude.ps1 │ ├── WriteVersion.ps1 │ ├── stage.py │ ├── xlOil_Install.ps1 │ ├── xlOil_NewAddin.ps1 │ ├── xlOil_RegistryClean.ps1 │ └── xlOil_Remove.ps1 └── xlOil.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a # Executables *.out *.app # Visual studio stuff .vs build/ packages/ graveyard/ *.vcxproj.user .vscode # Other caches __pycache__ .ipynb_checkpoints # External libraries /external/python/ /external/sqlite /external/pybind11-stubgen /external/CTPL /external/tomlplusplus /external/spdlog /external/pybind11 /external/rdcfswatcher /external/asmjit /external/winreg /docs/source/_build ~$* ================================================ FILE: .readthedocs.yml ================================================ # .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py build: os: "ubuntu-22.04" tools: python: "3.11" python: install: - requirements: docs/requirements.txt ================================================ FILE: .runsettings ================================================ build/x64/Debug ================================================ FILE: FUNDING.yml ================================================ github: [cunnane] ================================================ FILE: LICENSE ================================================ Apache License Copyright 2020 Steven Cunnane Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ xlOil ===== xlOil provides framework for interacting with Excel in different programming languages. It gives a way to write functions in a language and have them appear in Excel as worksheet functions or macros or have them control GUI elements. For example, the Python bindings can replace VBA in almost all use cases and even provide functionality not available from VBA. xlOil is designed to have very low overheads when calling your worksheet functions. xlOil supports different languages via plugins. The languages currently supported are: - C++ - Python - SQL In addition there is *xlOil_Utils* which contains some handy tools which Microsoft never quite got around to adding. The latest stable documentation is here: https://xloil.readthedocs.io/en/stable. xlOil features -------------- * Python - Concise syntax to declare an Excel function - Optional type checking of function parameters - Supports keyword arguments - Choice of globally declared functions or code modules limited to a single workbook like VBA workbook-level functions - Tight integration with *numpy* - very low overheads for array functions - Understands python tuples, lists, dictionarys and *pandas* dataframes - Async functions - RTD functions and on-the-fly RTD server creation - Macro type functions which write to ranges on the sheet - Access to the Excel Application object and the full Excel object model - Drive Excel through COM automation - Hook Excel events - Pass any python object back to Excel and back into another function - Simple add-in deployment - Two-way connection to *Jupyter* notebooks: run worksheet functions in *Jupyter* and query variables in the *jupyter* kernel - Create Ribbon toolbars and Custom Task Panes - Return *matplotlib* plots and images from worksheet functions * C++ - Safe and convenient wrappers around most things in the C-API - Concise syntax to declare Excel functions: registration is automatic - Deal with Excel variants, Ranges, Arrays and strings in a natural C++ fashion - Object cache allows returning opaque objects to Excel and passing them back to other functions - Simplified RTD server creation - RTD-based background calculation - Create Ribbon toolbars and Custom Task Panes * SQL - Create tables from Excel ranges and arrays - Query and join them with the full sqlite3 SQL syntax * Utils: very fast functions to: - Sort on multiple columns - Split and join strings - Make arrays from blocks Supporting other languages -------------------------- You can use xlOil as an end-user of these plugins or you can use it to write you own language bindings (and ideally add them to the repo). ================================================ FILE: Version.txt ================================================ 0.22.1 ================================================ FILE: config/xloil.ini ================================================ # ######## xlOil Settings ######### # # This is a TOML file. # ################################# # [Addin] ##### Plugins to load # # Load these plugins. The directory containing xloil.dll is searched # first, then the normal DLL search path order. # Plugins=["xlOil_Python", "xlOil_SQL", "xlOil_Utils"] # # Load any plugins in the same directory as the core dll which # match this pattern. # #PluginSearchPattern="xloil_*.dll" ##### Log file settings # # Intensity of logging, choose from: # "trace", "debug", "info", "warning", "error", "critical", "off" # LogLevel="warning" # # Level at which xlOil will pop up a log window to display recent # log entries # LogPopupLevel="error" # # Level at which xlOil will flush the log file to disk, lower levels # decrease performance but can be useful for debugging crashes # #LogFlushLevel="warning" # # The log file is created at .log and in the same # directory unless specified below # #LogFile="my.log" # # Log file rotation is controlled by these two parameters. A single # log file can grow to the *LogMaxSize* in Kb. Once this limit is # reached, a new blank log file is started, existing old log files # are renamed and the oldest is deleted to ensure maximum of # *LogNumberOfFiles* files. # #LogMaxSize=512 #LogNumberOfFiles=2 # If you have an ini file at %APPDATA%\xlOil\xlOil.ini, the core xlOil.dll # is loaded using those settings before any other xlOil-based XLL. Since only one # instance of xlOil can be hosted in Excel, one settings file must take precedence. # The assumption is that if you have xlOil installed, you want those settings to # be the primary ones. The below option, when used in a XLL-specific ini file, # allows it to be loaded before the core. # LoadBeforeCore=False ##### Date # # The date formats xlOil will attempt to parse for a string to date # conversion. Syntax follows C++ get_time here: # https://en.cppreference.com/w/cpp/io/manip/get_time # Note the date parsing is case sensitive because get_time is case # sensitive on Windows. # DateFormats=["%Y-%m-%d", "%Y%b%d"] # # The key XLOIL_PATH is edited by the xlOil_Install powershell script # Note: Use [[]] syntax because the order of Environment variables matters # [[Addin.Environment]] XLOIL_PATH="" [[Addin.Environment]] PATH='''%PATH%;%XLOIL_PATH%''' ##### Python Plugin Settings # [xlOil_Python] # # Python modules to load on start up. Must be on python's sys.path. # xlOil provides the following optional modules: # * xloil.xloil_ribbon: adds a ribbon toolbar with some useful functions # * xloil.jupyter: adds jupyter interaction # LoadModules=["xloil.xloil_ribbon"] # # On workbook open, look for a python file matching this template # where * is replaced by the Excel workbook name # WorkbookModule="*.py" # # Look for a module matching this pattern in the directory of the # XLL when it is loaded where '*' is replaced with the addin name. # The default is "*.py" which means an ini file is optional for a # simple addin # #AddinModule="*.py" # # Calls Py_SetPath before initialising python. This allows explict # specification of python's sys.path rather than using the usual # search routine, which overrides any setting of PYTHONPATH. # Generally this option is not required. # #SetSysPath='''.''' # # Sets the library for COM support. This is used by the `xloil.app` # and `to_com` functions although can be override on a per-call basis. # The default is 'win32com', 'comtypes' is also available. # #ComLib="comtypes" # # Selects the debugger to use. The choices are: # * Visual Studio 2019+ : no need to pre-select this, just attach to # a running Excel process # * VS Code: choose `vscode` or `debugpy`. The `DebugPyPort` attribute # then determines the port on which the server listens. # * Pdb: choose 'pdb' # Debugger = "" # # Determines the port on which the server listens. # DebugPyPort = "5678" # # By default xlOil loads python modules in a background thread when the addin # starts up or a local worksheet module is loaded. This avoids blocking the # UI but can lead to #NAME! errors if the a workbook is calculated before # the relevant functions are registered. Set UseLoaderThread to false to # disable background loading # #UseLoaderThread=false # # When error propagation is enabled, if any function argument is an error code # (#N/A!, #NUM!, etc.) that error code is given as the function's return value, # otherwise all argument values are handled by the function. Error propagation # is consistent with how Excel built-in functions handle errors, it improves # performance in the presence of error codes and it allow's Excel's error tracing # to function. # # Currently this setting is respected only by the xlOil_Python plugin. # #ErrorPropagation=false # # List of path prefixes which will be ignored for autoreloading on file # modification detection. # #AutoReloadExcludePaths = [] ##### Python Environment # # We need to set the python environment paths. Usually this is done automatically # by `xloil install` or `xloil create` or the ribbon. # # Usually just set PYTHONEXECUTABLE - python should be able to figure out the rest. # xlOil also uses this variable to help detect the presence of a virtual Environment. # Alternatively set the PYTHONPATH, PYTHONHOME and PATH directly as required by your # distribution. # # Note environment vars can be set by dynamically expanding a registry key. # [[xlOil_Python.Environment]] # # Tells python to do case-insensitve module name lookup and so minimises # surprises on windows when creating workbook and addin modules. Recommended! # PYTHONCASEOK="1" # # If this environment variable is set, xlOil tries to load the corresponding # pythonXY.dll as the interpreter. If unset or blank, xlOil uses the first python # version discovered using the paths set below. # XLOIL_PYTHON_VERSION="" # # Additional paths to add to python's sys.path. Prefer to add user search # paths here and reserve the PYTHONPATH setting below for system paths. # (We make the empty value a semi-colon so that the variable exists # for the environment variable expansion below) XLOIL_PYTHON_PATH=";" [[xlOil_Python.Environment]] # # PYTHONEXECUTABLE should point to the python.exe of the current environment. # Note it is possible to dynamically expand registry keys contained in angle # brackets <>. # PYTHONEXECUTABLE='''''' PYTHONPATH='''%PYTHONPATH%;%XLOIL_PYTHON_PATH%''' ================================================ FILE: config/xloil_local.ini ================================================ # ######## xlOil Dev Settings ########################################### # # This is a version of xlOil.ini which is copied to the build directory # when xloil.xll is built. Its purpose is to configure dev settings # without accidentally overwriting the distriuted xloil.ini file # ####################################################################### # [Addin] ##### Plugins to load # # Load these plugins. The directory containing xloil.dll is searched # first, then the normal DLL search path order. # Plugins=["xlOil_Python", "xlOil_SQL", "xlOil_Utils"] ##### Log file settings # # Intensity of logging, choose from: # "trace", "debug", "info", "warning", "error", "critical", "off" # LogLevel="trace" # # Level at which xlOil will pop up a log window to display recent # log entries # LogPopupLevel="error" # # Level at which xlOil will flush the log file to disk, lower levels # decrease performance but can be useful for debugging crashes # #LogFlushLevel="warning" # # The log file is created at .log and in the same # directory unless specified below # #LogFile="my.log" # # Log file rotation is controlled by these two parameters. A single # log file can grow to the *LogMaxSize* in Kb. Once this limit is # reached, a new blank log file is started, existing old log files # are renamed and the oldest is deleted to ensure maximum of # *LogNumberOfFiles* files. # #LogMaxSize=512 #LogNumberOfFiles=2 # If you have an ini file at %APPDATA%\xlOil\xlOil.ini, the core xlOil.dll # is loaded using those settings before any other xlOil-based XLL. The assumption # is that you have xlOil installed, but since only one instance of xlOil # can be hosted in Excel, one settings file must take precedence. The below option # allows an XLL to be loaded before the core. # LoadBeforeCore=false ##### Date # # The date formats xlOil will attempt to parse for a string to date # conversion. Syntax follows C++ get_time here: # https://en.cppreference.com/w/cpp/io/manip/get_time # Note the date parsing is case sensitive because get_time is case # sensitive on Windows. # DateFormats=["%Y-%m-%d", "%Y%b%d"] # # The key XLOIL_PATH is edited by the xlOil_Install powershell script # Note: Use [[]] syntax because the order of Environment variables matters # #[[Addin.Environment]] #XLOIL_PATH="" #[[Addin.Environment]] #PATH='''%PATH%;%XLOIL_PATH%''' ##### Python Plugin Settings # [xlOil_Python] # # Python modules to load on start up. Must be on python's sys.path # LoadModules=["xloil.xloil_ribbon"] # # On workbook open, look for a python file matching this template # where * is replaced by the Excel workbook name # WorkbookModule="*.py" # # Look for a module matching this pattern in the directory of the # XLL when it is loaded where '*' is replaced with the addin name. # The default is "*.py" which means an ini file is optional for a # simple addin # #AddinModule="*.py" # # Calls Py_SetPath before initialising python. This allows explict # specification of python's sys.path rather than using the usual # search routine, which overrides any setting of PYTHONPATH. # Generally this option is not required. # #SetSysPath='''.''' # # Sets the library for COM support. This is used by the `xloil.app` # and `to_com` functions although can be override on a per-call basis. # The default is 'win32com', 'comtypes' is also available. # #ComLib="comtypes" # # Selects the debugger to use. The choices are: # * Visual Studio 2019+ : no need to pre-select this, just attach to # a running Excel process # * VS Code: choose `vscode` or `debugpy`. The `DebugPyPort` attribute # then determines the port on which the server listens. # * Pdb: choose 'pdb' # Debugger = "" # # Determines the port on which the server listens. # DebugPyPort = "5678" #UseLoaderThread=false ErrorPropagation=false ##### Python Environment # # We need to set the python environment paths. Usually this is done automatically # by `xloil install` or `xloil create` or the ribbon. # # Usually just set PYTHONEXECUTABLE - python should be able to figure out the rest. # xlOil also uses this variable to help detect the presence of a virtual Environment. # Alternatively set the PYTHONPATH, PYTHONHOME and PATH directly as required by your # distribution. # # Note environment vars can be set by dynamically expanding a registry key. # [xlOil_Python.Environment] PYTHONCASEOK="1" # # If this environment variable is set, xlOil tries to load the corresponding # pythonXY.dll as the interpreter. If unset or blank, xlOil uses the reported # python version of the interpreter in the first python dist found. # XLOIL_PYTHON_VERSION="" # # PYTHONEXECUTABLE should point to the python.exe of the current environment. # Note it is possible to dynamically expand registry keys contained in angle # brackets <>. # PYTHONEXECUTABLE='''D:\lib\conda311\python.exe''' #PYTHONEXECUTABLE='''''' XLOIL_PYTHON_PATH='''D:\lib''' PYTHONPATH='''%PYTHONPATH%;D:\lib''' ================================================ FILE: config/xloil_local_32.ini ================================================ # ######## xlOil Dev Settings ########################################### # # This is a version of xlOil.ini which is copied to the build directory # when xloil.xll is built. Its purpose is to configure dev settings # without accidentally overwriting the distriuted xloil.ini file # ####################################################################### # [Addin] ##### Plugins to load # # Load these plugins. The directory containing xloil.dll is searched # first, then the normal DLL search path order. # Plugins=["xlOil_Python", "xlOil_SQL", "xlOil_Utils"] ##### Log file settings # # Intensity of logging, choose from: # "trace", "debug", "info", "warning", "error", "critical", "off" # LogLevel="trace" # # Level at which xlOil will pop up a log window to display recent # log entries # LogPopupLevel="error" # # The log file is created at .log and in the same # directory unless specified below # #LogFile="my.log" # # Log file rotation is controlled by these two parameters. A single # log file can grow to the *LogMaxSize* in Kb. Once this limit is # reached, a new blank log file is started, existing old log files # are renamed and the oldest is deleted to ensure maximum of # *LogNumberOfFiles* files. # #LogMaxSize=512 #LogNumberOfFiles=2 # If you have an ini file at %APPDATA%\xlOil\xlOil.ini, the core xlOil.dll # is loaded using those settings before any other xlOil-based XLL. The assumption # is that you have xlOil installed, but since only one instance of xlOil # can be hosted in Excel, one settings file must take precedence. The below option # allows an XLL to be loaded before the core. # LoadBeforeCore=false ##### Date # # The date formats xlOil will attempt to parse for a string to date # conversion. Syntax follows C++ get_time here: # https://en.cppreference.com/w/cpp/io/manip/get_time # Note the date parsing is case sensitive because get_time is case # sensitive on Windows. # DateFormats=["%Y-%m-%d", "%Y%b%d"] # # The key XLOIL_PATH is edited by the xlOil_Install powershell script # Note: Use [[]] syntax because the order of Environment variables matters # #[[Addin.Environment]] #XLOIL_PATH="" #[[Addin.Environment]] #PATH='''%PATH%;%XLOIL_PATH%''' ##### Python Plugin Settings # [xlOil_Python] # # Python modules to load on start up. Must be on python's sys.path # LoadModules=["xloil.xloil_ribbon", "excelfunc"] # # On workbook open, look for a python file matching this template # where * is replaced by the Excel workbook name # WorkbookModule="*.py" # # Look for a module matching this pattern in the directory of the # XLL when it is loaded where '*' is replaced with the addin name. # The default is "*.py" which means an ini file is optional for a # simple addin # #AddinModule="*.py" # # Calls Py_SetPath before initialising python. This allows explict # specification of python's sys.path rather than using the usual # search routine, which overrides any setting of PYTHONPATH. # Generally this option is not required. # #SetSysPath='''.''' # # Sets the library for COM support. This is used by the `xloil.app` # and `to_com` functions although can be override on a per-call basis. # The default is 'win32com', 'comtypes' is also available. # #ComLib="comtypes" # # Selects the debugger to use. The choices are: # * Visual Studio 2019+ : no need to pre-select this, just attach to # a running Excel process # * VS Code: choose `vscode` or `debugpy`. The `DebugPyPort` attribute # then determines the port on which the server listens. # * Pdb: choose 'pdb' # Debugger = "" # # Determines the port on which the server listens. # DebugPyPort = "5678" #UseLoaderThread=false ##### Python Environment # # We need to set the python environment paths. Usually this is done automatically # by `xloil install` or `xloil create` or the ribbon. # # Usually just set PYTHONEXECUTABLE - python should be able to figure out the rest. # xlOil also uses this variable to help detect the presence of a virtual Environment. # Alternatively set the PYTHONPATH, PYTHONHOME and PATH directly as required by your # distribution. # # Note environment vars can be set by dynamically expanding a registry key. # [xlOil_Python.Environment] PYTHONCASEOK="1" # # If this environment variable is set, xlOil tries to load the corresponding # pythonXY.dll as the interpreter. If unset or blank, xlOil uses the reported # python version of the interpreter in the first python dist found. # XLOIL_PYTHON_VERSION="" # # PYTHONEXECUTABLE should point to the python.exe of the current environment. # Note it is possible to dynamically expand registry keys contained in angle # brackets <>. # PYTHONEXECUTABLE='''D:\lib\Python311-32\python.exe''' #PYTHONEXECUTABLE='''''' XLOIL_PYTHON_PATH='''D:\lib''' PYTHONPATH='''%PYTHONPATH%;D:\lib''' ================================================ FILE: docs/make.cmd ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) if "%1" == "" goto help set XLOIL_SOLN_DIR=%~dp0.. set DOC_BUILD_DIR=%XLOIL_SOLN_DIR%\build\docs set PY_PACKAGE_DIR=%XLOIL_SOLN_DIR%\libs\xlOil_Python\Package set SOURCEDIR=%~dp0\source set PATH=%PATH%;C:\Program Files\doxygen\bin %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) mkdir "%DOC_BUILD_DIR%" if "%1" == "doxygen" goto doxygen if "%1" == "-bin" ( set XLOIL_BIN_DIR=%XLOIL_SOLN_DIR%\build\%2 shift shift ) REM Generate the doc stubs: since we can import the core pyd locally, this is REM really just for ReadTheDocs if exist "%PY_PACKAGE_DIR%\generate_stubs.py" ( echo.Generating doc stubs for xloil_core python "%PY_PACKAGE_DIR%\generate_stubs.py" ) REM It's very important to pass the -E argument to sphinx, otherwise it does REM not notice changes to docstrings in python modules and generates the REM wrong documentation REM Set READTHEDOCS so we get consistency with the RTD online version set READTHEDOCS=1 %SPHINXBUILD% -M %1 "%SOURCEDIR%" "%DOC_BUILD_DIR%" %SPHINXOPTS% -E -W --keep-going %O% set READTHEDOCS= goto end :doxygen pushd source REM For some reason doxygen can't create directories itself mkdir "%DOC_BUILD_DIR%\doxygen\html\doxygen" mkdir "%DOC_BUILD_DIR%\doxygen\xml\doxygen" REM Need to reverse slashes for doxygen because life is tough set "XLO_SOLN_DIR=%XLOIL_SOLN_DIR:\=/%" doxygen xloil.doxyfile popd goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %DOC_BUILD_DIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: docs/requirements.txt ================================================ numpy numpydoc autodocsumm sphinx-rtd-theme docutils>=0.16 sphinx_rtd_theme autodocsumm ================================================ FILE: docs/source/Concepts.rst ================================================ ============== xlOil Concepts ============== This document explains some of the key xlOil concepts shared accross different languages .. contents:: :local: Excel Functions (UDFs) ---------------------- Excel supports several classes of user-defined functions: - Macros: run at user request, have write access to workbook - Worksheet functions: run by Excel's calculation cycle. Several sub-types: - Vanilla - Thread-safe: can be run concurrently - Macro-type: can read from sheet addresses and invoke a wider variety of Excel interface functions - Async: can run asynchronously during the calc cycle, but not in the background - RTD: (real time data) background threads which push data onto the sheet when it becomes available - Cluster: can be packaged to run on a remote Excel compute cluster xlOil currently supports all but Cluster functions. Excel can pass functions / macros data in one of these types: - Integer - Boolean - Floating point - String - Error, e.g. #NUM!, #VALUE! - Empty - Array of any of the above - Range refering to a worksheet address There is no date type. Excel's builtin date functions interpret numbers as days since 1900. Excel does not support timezones. .. _core-cached-objects: Cached Excel Objects -------------------- xlOil has an internal lookup for Excel values, which is a convenient way of passing arrays around a sheet and as arguments to other xlOil functions. The function ``=xloRef(A1:B2)`` returns a cache string of the form: ``[WorkbookName]SheetName!CellRef,#`` The data can be recovered using ``=xloVal()``. Alternatively, this string can be passed instead of the source range to xlOil functions which support cache lookups - and for large arrays this is much faster. Example use cases are: * Where you might use a named range - to avoid updating references in multiple functions when data is appended. Because `xloRef` automatically trims the range back to the last non-blank row, it can be pointed to a range far larger than the data. * To speed up passing the same large array into several functions (e.g. multiple lookups from a data table, although consider xlOil_SQL if you want to do this). However, there is a disadvantage to using `xloRef`: the cache is cleared when a workbook is closed, but Excel does not know to recalculate the `xloRef` functions when the workbook is reopened. Hence you need to force a sheet recalculation using *Ctrl-Alt-F9*. In addition to caching arrays, xlOil plugins use the cache to opaquely return referencs to in-memory structures. Although the strings look similar, they point to very different objects and cannot be written to the sheet using `xloVal`. .. _concepts-rtd-async: Rtd / Async ----------- In Excel, RTD (real time data) functions are able to return values independently of Excel's calculation cycle. Excel has supported RTD functions since at least Excel 2002. In Excel 2010, Excel introduced native async functions. RTD: * Pro: operates independently of the calc cycle - true background execution * Pro: provides notification when an RTD function call is changed or removed * Con: increased overhead compared to native async * Con: requires automatic calculation enabled (or repeated presses of F9 until calc is done) Native async: * Pro: Less overhead compared to RTD * Pro: works with manual calc mode * Con: tied to calc cycle, so any interruption cancels all asyncs functions The last con is particularly problematic for native async: *any* user interaction with Excel will interrupt the calc, so whilst native async functions can run asynchronously with each other, they cannot be used to perform background calculations. .. _concepts-ribbon: Custom UI: The Fluent Ribbon ---------------------------- xlOil allows dynamic creation of Excel Ribbon components. The ribbon is defined by XML (surrounded with tags) which should be created with a specialised editor, see the *Resources* below. Controls in the ribbon interact with user code via callback handlers. These callbacks pass a variety of arguments and may expect a return value; it is important to check that the any callback behaves as per the callback specifications in the *Resources*. To pass ribbon XML into Excel, xlOil creates a COM-based add-in in addition to the XLL-based add-in which loads the xlOil core - you can see this appearing in Excel's add-in list in the Excel's Options windows. Resources: * `Microsoft: Overview of the Office Fluent Ribbon `_ * `Microsoft: Customizing the Office Fluent Ribbon for Developers `_ * `Microsoft: Custom UI XML Markup Specification `_ * `Microsoft: Ribbon Callback Specifications `_ * `Office RibbonX Editor `_ * `Ron de Bruin: Ribbon Examples files and Tips `_ .. _concepts-intellisense: Intellisense / Function Context Help ------------------------------------ To get pop-up function help, install `Excel-DNA Intellisense `_ In addition to the function wizard, Excel has a function context help which pops up as you type. This is only directly available for built-in functions. Fortunately, Govert of *Excel-DNA* has created an add-in which mimics this behaviour for user-defined functions. Download the XLL (with the right bitness) from the link above and activate it by one of: 1) Dragging the XLL into Excel (must be done once per session) 2) Install it via *Excel* => *Options* => *Addins* => *Manage*. 3) Copy it to your `%APPDATA%\\Microsoft\\Excel\\XLSTART` directory. You will now get pop-up help for all xlOil plugins functions, including any dynamically registered ones, for example, created with python plugin. ================================================ FILE: docs/source/Core.rst ================================================ =========== xlOil Core =========== Here we tell you how to get started using xlOil and how to distribute addins .. _core-getting-started: Getting Started --------------- You need **Excel 2010** or later on a desktop PC. xlOil will not work with online or Mac versions of Office. .. important:: If you want to use xlOil with python you should install it via `pip`. Stop reading this page and go to :doc:`xlOil_Python/GettingStarted` Download the binary package (e.g. from gitlab) and unzip to a directory of your choice. You can run the `xlOil_Install.ps1` script to install the addin for every Excel session, or just drop `xlOil.xll` into any running Excel session to load xlOil temporarily. xlOil should now load when you open Excel, try following :any:`sql-getting-started` To configure the plugins being loaded, see :any:`core-edit-settings-files`. Some plugings, such as the python one, have several paths which must be set correctly - it will generally be easier to use these plugins by following their specific installation instructions. .. _core-example-sheets: Example sheets -------------- To check your setup and see some of the capabilities of xlOil, try: :download:`Tests and Examples `. .. _core-edit-settings-files: Editing the settings file ------------------------- There is an `xlOil.ini` file linked to the main `xlOil.xll` addin. (This ini file is actually parsed as TOML, an extension of the ini format). xlOil searches for this file first in `%APPDATA%/xlOil` then in the directory containing the `xlOil.xll` addin. The two most important setting in `xlOil.ini` are: :: ... XLOIL_PATH='''C:\lib\xloil``` ... Plugins=["xloil_Python.dll", "xlOil_SQL.dll"] ``XLOIL_PATH`` allows the `xlOil.xll` addin to locate the main xlOil DLL if the addin is being run from a different directory. When the main DLL has loaded, xlOil loads the specified plugins. It searches for these plugins first in the directory containing `xlOil.dll`, then the directory containing the XLL, then the usual DLL search paths. Setting enviroment variables in settings files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each plugin can have an *Environment* section in the settings file. Within this block tokens are interpreted as enviroment variables to set. A plugin's environment settings are processed before the plugin is loaded. Keys are interpreted as environement variables to set. Values can reference other enviroment variables by surrounding the name with `%` characters. In addition you can pull values from the registry by surrounding the registry path with angle brackets `<>`. Leaving a trailing backslash `\\` in the registry path fetches the default value for that key. The TOML syntax of three single quotes indicates a string literal, this avoids escaping all the backslashes. The default enviroment block for Python looks like this: :: [[xlOil_Python.Environment]] xlOilPythonVersion="3.7" [[xlOil_Python.Environment]] PYTHONPATH='''''' PYTHON_LIB='''''' [[xlOil_Python.Environment]] PATH='''%PATH%;%PYTHON_LIB%;%PYTHON_LIB%\Library\bin''' The double brackets tell TOML that the order of these declarations is important, this means we can refer to previously set enviroment variables. In addition you can pull values from the registry by surrounding the registry path with angle brackets `<>`, for example, ``. Leaving a trailing backslash `\\` in the registry path fetches the default value for that key. Troubleshooting --------------- You may need to tweak your settings file: :any:`core-edit-settings-files` A common problem is that the COM interface misbehaves by either failing on start-up or failing because of an open dialog box in Excel. For a start-up fail, unload and reload the addin. For other errors try to close dialog boxes or panes and if that fails, restart Excel. The log file ~~~~~~~~~~~~~ If xlOil detects a serious load error, it pops up a log window to alert you (this can be turned off). If it succesfully loaded the core DLL a log file will also be created next to `xlOil.ini`, which by default is in ``%APPDATA%\xlOil``. If xlOil loaded, the worksheet function `xloLog` can tell you where this file is. A setting in `xlOil.ini` controls the log level. Manual installation ~~~~~~~~~~~~~~~~~~~ The `xlOil_Install.ps1` script does the following: 1. Check xlOil is not in Excel's disabled add-ins 2. Copy xlOil.xll to the ``%APPDATA%\Microsoft\Excel\XLSTART`` directory 3. Copy xlOil.ini in the ``%APPDATA%\xlOil``` directory 4. Check VBA Object Model access is allowed in `Excel > File > Options > Trust Center > Trust Center Settings > Macro Settings`` Manual removal ~~~~~~~~~~~~~~ Should you need to force remove xlOil, do the following: 1. Remove *xlOil.xll* from ``%APPDATA%\Microsoft\Excel\XLSTART`` 2. Remove the directory ``%APPDATA%\xlOil``` If you have added *xlOil.xll* or another xll add-in (xlOil does not do this by default) and you want to remove it go to: 1. `Excel > File > Options > Add-ins > Manage Excel Addins` 2. If the previous step fails to remove the addin, start Excel with elevated/admin priviledges and retry 3. If that fails, try to remove the add-in from the registry key ``HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\\Excel\Options``. You should see values *OPEN*, *OPEN1*, etc with add-in names to be loaded. After removing a value, you need to rename the others to preserve the numeric sequence. 4. If that does not work, also look at this registry key: ``HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\\Excel\Options``. Note you may need to run the registry editor with elevated priviledges. To really scrub the registry, you may find references to the addin under: * `HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Office\\\\Excel\\Add-in Manager` * `HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Office\\\\Excel\\AddInLoadTimes` * `HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Office\\\\Excel\\Resiliency\DisabledItems` * `HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Office\\Excel\\Addins` * `HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Office\\Excel\\AddinsData` ================================================ FILE: docs/source/Developer.rst ================================================ ======================= xlOil Development Guide ======================= Getting started as developer ---------------------------- - You need Visual Studio 2022 or newer - All xlOil_Core dependencies are already in the `external` folder. Some of them are compressed, so unpack them. - To build the `xlOil-COM` library you need the ATL headers which can be installed with the Visual Studio installer (under C++ development). - For debugging, set xlOil_Loader as the target project, with command=`` args=`$(OutDir)\xloil.xll` Release Instructions -------------------- :: cd \tools python stage.py (Optional) test python wheels with :: cd \build\staging\pypackage pip install dist/xlOil-0.3-cp37-cp37m-win_amd64.whl pip uninstall dist/xlOil-0.3-cp37-cp37m-win_amd64.whl Use twine to upload to PyPI (note you need to ensure twine has the right login keys/secrets): :: cd \build\staging\pypackage # (Optional test) twine upload --repository-url https://test.pypi.org/legacy/ dist/* # The real thing twine upload dist/* ================================================ FILE: docs/source/Events.rst ================================================ ================== xlOil Excel Events ================== .. contents:: :local: Introduction ------------ xlOil allows code to hook into Excel events. The Excel API documentation `Excel.Application `_ contains more complete descriptions of most of these events, with the exception of the `CalcCancelled`, `WorkbookAfterClose`, `XllAdd` and `XllRemove` events which are not part of the *Application* object and are provided by xlOil. For the syntax used to hook these events, see the individual language documentation. Parameter types =============== Most events use some of the parameter types described below: * *Workbook name*: passed as a string (not a ``Workbook`` object) * *Worksheet name*: passed as a string * *Range*: passed as a ``Range`` object * *Cancel*: a bool which starts as False If the event handler sets this argument to True, further processing of the event will stop AfterCalculate -------------- This event occurs after all Worksheet.Calculate, Chart.Calculate, QueryTable.AfterRefresh, and SheetChange events. It's the last event to occur after all refresh processing and all calc processing have completed, and it occurs after CalculationState is set to xlDone. This event is called if the calculation was interrupted (for example by the user pressing a key or mouse button). WorkbookOpen ------------ Occurs when a workbook is opened. Takes a single parameter containing the workbook name. NewWorkbook ----------- Occurs when a new workbook is created. Takes a single parameter containing the workbook name. SheetSelectionChange -------------------- Occurs when the selection changes on any worksheet (doesn't occur if the selection is on a chart sheet). Takes two paramters: * Worksheet name * Target / selected Range SheetBeforeDoubleClick ---------------------- Occurs when any worksheet is double-clicked, before the default double-click action. Takes three parameters * Worksheet name * Target - A Range giving cell nearest to the mouse pointer when the double-click occurred. * Cancel - False when the event occurs. If the event procedure sets this argument to True, the default double-click action isn't performed when the procedure is finished. SheetBeforeRightClick --------------------- Occurs when any worksheet is right-clicked, before the default right-click action. Takes three parameters * Worksheet name * Target - A Range giving the cell nearest to the mouse pointer when the right-click occurred. * Cancel - False when the event occurs. If the event procedure sets this argument to True, the default right-click action isn't performed when the procedure is finished. SheetActivate ------------- Occurs when any sheet is activated. Takes a single parameter containing the sheet name. SheetDeactivate --------------- Occurs when any sheet is deactivated. Takes a single parameter containing the sheet name. SheetCalculate -------------- Occurs after any worksheet is recalculated or after any changed data is plotted on a chart. Takes a single parameter containing the sheet name. SheetChange ----------- Occurs when cells in any worksheet are changed by the user or by an external link. Takes two paramters: * Worksheet name * Changed Range WorkbookActivate ---------------- Occurs when any workbook is activated. Takes a single parameter containing the workbook name. WorkbookDeactivate ------------------ Occurs when any workbook is deactivated. Takes a single parameter containing the workbook name. WorkbookBeforeClose ------------------- Occurs immediately before any open workbook closes. Takes two parameters * Workbook name * Cancel - False when the event occurs. If the event procedure sets this argument to True, the workbook doesn't close when the procedure is finished. The event is not called for each workbook when Excel exits. WorkbookBeforeSave ------------------ Occurs before any open workbook is saved. Takes three parameters: * Workbook name * SaveAsUI - True if the Save As dialog box will be displayed due to changes made that need to be saved in the workbook. * Cancel - False when the event occurs. If the event procedure sets this argument to True, the workbook isn't save when the procedure is finished. WorkbookBeforePrint ------------------- Occurs immediately before any open workbook is printed. Takes two parameters * Workbook name * Cancel - False when the event occurs. If the event procedure sets this argument to True, the workbook doesn't print when the procedure is finished. WorkbookAfterClose ------------------ Excel's *WorkbookBeforeClose* event is cancellable by the user so it is not possible to know if the workbook actually closed. When xlOil calls `WorkbookAfterClose`, the workbook is certainly closed, but it may be some time since that closure happened. Takes a single parameter containing the workbook name. The event is not called for each workbook when xlOil exits. This event is not part of the *Excel.Application* API. WorkbookNewSheet ---------------- Occurs when a new sheet is created in any open workbook. The first parameter is the workbook name, the second is the new sheet name. WorkbookAddinInstall -------------------- Occurs when a workbook is installed as an add-in. Takes a single parameter containing the workbook name. WorkbookAddinUninstall ---------------------- Occurs when any add-in workbook is uninstalled. Takes a single parameter containing the workbook name. CalcCancelled ------------- Called when the calculation cycle is cancelled (for example by the user pressing a key or mouse button). Native async functions should stop any background calculation when this event is received. This event is not part of the *Excel.Application* API. XllAdd ------ Triggered when an XLL related to this instance of xlOil is added by the user using the Addin settings window. The parameter is the XLL filename. This event is not part of the *Excel.Application* API. XllRemove --------- Triggered when an XLL related to this instance of xlOil is removed by the user using the Addin settings window. The parameter is the XLL filename. This event is not part of the *Excel.Application* API. ================================================ FILE: docs/source/Introduction.rst ================================================ ===== xlOil ===== xlOil is a framework for linking programming languages with Excel language. That is, a way to write functions in a language of your choice and have them appear in Excel as worksheet functions and macros, as well as manipulating the application and GUI similar to VBA. xlOil is designed to have very low overheads when calling your own worksheet functions. xlOil supports different languages via plugins. The languages currently supported are: - C++ - Python - SQL In addition there is :any:`xlOil_Utils` which contains some handy tools which Microsoft never quite got around to adding. You can use xlOil as an end-user of these plugins or you can use it to write you own language bindings and contribute. .. important:: **Start Here:** :any:`core-getting-started` xlOil features -------------- * Python - Very concise syntax to declare an Excel function - Optional type checking of function parameters - Supports keyword arguments - Choice of globally declared functions or code modules limited to a single workbook (just like VBA workbook-level functions) - Tight integration with numpy - very low overheads for array functions - Understands python tuples, lists, dictionarys and pandas dataframes - Async functions - RTD functions and on-the-fly RTD server creation - Macro type functions which write to ranges on the sheet - Access to the Excel Application object - Hook Excel events - Pass any python object back to Excel and then back into any python function - Simple and quick add-in deployment - Two-way connection to Jupyter notebooks: run worksheet functions in Jupyter and query variables in the jupyter kernel - On-the-fly creation/destruction of COM addin objects and Ribbon UI * C++ - Safe and convenient wrappers around most things in the C-API - Concise syntax to declare Excel functions: registration is automatic - Deal with Excel variants, Ranges, Arrays and strings in a natural C++ fashion - Object cache allows returning opaque objects to Excel and passing them back to other functions - Async functions - RTD functions and on-the-fly RTD server creation - On-the-fly creation/destruction of COM addin objects and Ribbon UI * SQL - Create tables from Excel ranges and arrays - Query and join them with the full sqlite3 SQL syntax * Utils: very fast functions to - Sort on multiple columns - Split and join strings - Make arrays from blocks Why xlOil was created --------------------- Programming with Excel has no ideal solution. You have a choice of APIs: - VBA - great integration with Excel, but clunky syntax compared with other languages, few common libraries, limited IDE, testing and source control very difficult, single-threaded, use not encouraged by MS. - C-API - It's C and hence unsafe. The API is also old, has some quirks and is missing many features of the other APIs. But it's the fastest interface. - COM - More fully-featured but slower and strangely missing some features of the C-API. It's C++ so still pretty unsafe. Has some unexpected behaviour and may fail to respond. Requires COM binding support in your language or the syntax is essentially unusuable. - .Net API - actually sits on top of COM, the best modern interface but only for .Net languages and speed-limited by COM overheads, still missing some C-API features. - Javascript API - supports Office on multiple devices and operating system, but very slow with limited functionality compared to other APIs. xlOil tries to give you the C and COM APIs blended in a more friendly fashion and adds: - Solution to the "how to register a worksheet function without a static DLL entry point" problem - Object caching - A framework for converting excel variant types to another language and back - A convenient way of creating worksheet-scope functions - A loader stub - Goodwill to all men Comparison between xlOil and other Excel Addin solutions -------------------------------------------------------- Given the age of Excel, there are many other solutions for Excel add-in development. Most available packages are commercial (and quite expensive) so I cannot test relative performance with xlOil. They are generally focussed on a single language rather than providing a framework for language bindings as xlOil does. The following, likely incomplete, list includes some of the most prominent Excel addin software. - Addin express: (commercial) fully-featured with nice GUI and the support seems good as well. Limited to .Net languages, but covers the entire Office suite (not just Excel) - ExcelDNA: (free) mature, well-regarded and widely used, covers almost all Excel API features, but only for .Net languages. I strongly recommend this software if you are using .Net languages. - XLL Plus: (commercial) seems to be fully-featured with GUI wizards to help developers, but only for C++ and the most expensive sofware here. - PyXLL: (commercial) Python-only. Supports the full range of Excel API features and some functionality to run on remote servers. - XlWings: (mostly free) Python-only. More mature sofware, but considerably slower (2000x in my test case) than xlOil due to use of slower APIs. Can only create 'local' functions backed by VBA, so every Excel sheet needs to be be a macro sheet with special VBA redirects. This means it is not viable for addin deployment. Supports Mac and Python 2.7 (but licence fee required for this). ================================================ FILE: docs/source/conf.py ================================================ # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) import os import sys from pathlib import Path # # The default soln_dir and bin_dir paths here are to allow VS code to auto # preview the docs. I'm currently not sure how to add environment variables # to processes run by VS code. # file_dir = Path(__file__).parent xloil_dir = file_dir.parent.parent soln_dir = Path(os.environ.get("XLOIL_SOLN_DIR", xloil_dir)).resolve() bin_dir = Path(os.environ.get("XLOIL_BIN_DIR", xloil_dir / "build/x64/Debug")).resolve() if bin_dir.exists(): sys.path.append(str(bin_dir)) sys.path.append(str(soln_dir / "libs/xlOil_Python/Package")) # May as well fail here if we can't find xloil try: import xloil except Exception as e: raise Exception(f'{e}. SysPath={sys.path}', e) # -- Project information ----------------------------------------------------- project = 'xlOil' copyright = '2022, Steven Cunnane' author = 'Steven Cunnane' # The full version, including alpha/beta/rc tags release = (soln_dir / "Version.txt").read_text() # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.autosectionlabel", "numpydoc", 'autodocsumm' ] # Avoid duplicate label warnings from autosectionlabel suppress_warnings = ['autosectionlabel.*'] # True to prefix each section label with the name of the document it is in, # followed by a colon. For example, index:Introduction for a section called # Introduction that appears in document index.rst. autosectionlabel_prefix_document = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. USE_READTHEDOCS_THEME = True if USE_READTHEDOCS_THEME: import sphinx_rtd_theme extensions.append('sphinx_rtd_theme') html_theme = "sphinx_rtd_theme" else: html_theme = 'bizstyle' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = []# ['_static'] # A list of paths that contain extra files not directly related to the documentation, # such as robots.txt or .htaccess. Relative paths are taken as relative to the # configuration directory. They are copied to the output directory. #html_extra_path = ['../build/doxygen'] autodoc_default_flags = ['members'] # # If autosummary_generate is True, stub .rst files for for items in the autosummary # toctree are automatically generated. We prefer to keep a flatter structure. # autosummary_generate = False # See https://stackoverflow.com/questions/34216659/ numpydoc_show_class_members=False # # Required for readthedocs build as the master_doc seems to default to 'contents' in # their environment. Locally sphinx builds fine without this # master_doc = 'index' # -- Generate examples file --------------------------------------------------- # # Only do this when XLOIL_BIN_DIR is explicitly set so not on ad-hoc run # if "XLOIL_BIN_DIR" in os.environ: import zipfile from zipfile import ZipFile try: os.makedirs('_build') except FileExistsError: pass zipObj = ZipFile('_build/xlOilExamples.zip', 'w', compression=zipfile.ZIP_BZIP2) try: zipObj.write(soln_dir / "tests" / "AutoSheets" / "PythonTest.xlsm", "PythonTest.xlsx") zipObj.write(soln_dir / "tests" / "ManualSheets" / "python" / "PythonTestAsync.xlsm", "PythonTestAsync.xlsm") zipObj.write(soln_dir / "tests" / "AutoSheets" / "PythonTest.py", "PythonTest.py") zipObj.write(soln_dir / "tests" / "AutoSheets" / "TestModule.py", "TestModule.py") zipObj.write(soln_dir / "tests" / "AutoSheets" / "TestSQL.xlsx", "TestSQL.xlsx") zipObj.write(soln_dir / "tests" / "AutoSheets" / "TestUtils.xlsx", "TestUtils.xlsx") except FileNotFoundError as err: print("WARNING: Could not create xlOilExamples.zip due to: ", str(err)) zipObj.close() ================================================ FILE: docs/source/index.rst ================================================ .. raw:: html xlOil documentation =================== .. toctree:: :maxdepth: 4 :caption: Contents: :glob: Introduction Core Concepts Events xlOil_Core_Functions xlOil_Cpp/index xlOil_Python/index xlOil_SQL/index xlOil_Utils Developer Indices and tables ================== * :ref:`genindex` * :ref:`search` ================================================ FILE: docs/source/xlOil_Core_Functions.rst ================================================ ========================= xlOil Core Functions ========================= This page describes the worksheet functions registered by the xlOil core, which should always be available when xlOil is running. xloRef: adds a value or array to the cache ------------------------------------------ .. function:: xloRef(arrayOrRange) Adds the specified value or range or array to the object cache and returns a string reference. See :any:`core-cached-objects` xloVal: Retrives a value from the Excel data cache -------------------------------------------------- .. function:: xloVal(cacheRefString) Given a reference string, returns a stored array or value. Cached values are not saved with the workbook so will need to be recreated by forcing a full recalc (press Ctrl-Alt-F9) when the workbook is opened See :any:`core-cached-objects` xloHelp: returns help on an xlOil registered function ------------------------------------------------------ .. function:: xloHelp(functionName) Returns a two column array containing the help information passed to Excel's function wizard when the function was registered. The first row contains the function name and the main help string. Subsequent rows contain argument names and their associated help. In the function wizard all help strings are limited to 256 characters. xloHelp does not have this limitation. xloLog: flushes the log file and returns its location ----------------------------------------------------- .. function:: xloLog(ShowWindow=FALSE) The xlOil log is only flushed (written to file) occasionally or when an error or warning occurs. Executing this function flushes the log and returns the location of the log file (by default this is the same directory as the settings file). Setting the *ShowWindow* parameter causes xlOil's log window to appear. This doesn't display the contents of the entire log file but only the most recent log messages. xloVersion: returns information on the xlOil version ---------------------------------------------------- .. function:: xloVersion() Returns a two-column array of version information. ================================================ FILE: docs/source/xlOil_Cpp/COM.rst ================================================ ======================= xlOil C++ Using COM ======================= The root of Excel's COM interface is `Excel.Application `_ which VBA programmers will be familiar with. The easiest way to work with it in C++ is by importing the type library into your source file - this allows compile time checks and auto-completion and results in faster code. COM objects can be accessed using 'late-binding' where methods and properties are queried from the object at runtime, but this clearly limits performance. In xlOil, the appropriate type library can be imported with the *ExcelTypeLib.h* header: :: #include #include using namespace xloil; XLO_FUNC_START(testToday()) { excelApp().Range[L"A1"]->NumberFormat = L"dd-mm-yyyy"; } XLO_FUNC_END(testToday).macro(); If *ExcelTypeLib.h* is included before `xlOil.h` it adds COM error handling to any worksheet functions declared with `XLO_FUNC_START`. This is useful because COM throws its own `_com_error` classes which do not derive from `std::exception`. I cannot find a specific Excel/COM/C++ tutorial at this time, but the *Excel.Application* API documentation is comprehensive. ================================================ FILE: docs/source/xlOil_Cpp/CppRtd.rst ================================================ ======================= xlOil C++ RTD functions ======================= In Excel, RTD (real time data) functions are able to return values independently of Excel's calculation cycle. The classic example of this is a stock ticker with live prices. This is contrasted with Excel's native async support in :any:`concepts-rtd-async`. Example: Subscription --------------------- xlOil wraps the RTD server implementation to make subscription and publication straightfoward as in the following example: .. highlight:: c++ :: #include using namespace xloil; IRtdServer& myRtdServer() { static shared_ptr ptr = newRtdServer(); static auto backgroundTask = std::async([]() { // Somehow fetch live prices here ptr->publish("SP500", ExcelObj(123)); ptr->publish("FTSE100", ExcelObj(456)); }); return *ptr; } XLO_FUNC_START( getStock(const ExcelObj& ticker) ) { auto value = myRtdServer().subscribe(tag.toString().c_str()); return returnValue(value ? *value : Const::Error(CellError::NA)); } XLO_FUNC_END(getStock); Example: Background task ------------------------ The RTD mechanism can be used to run slow tasks like data fetches or calculations in the background whilst the user continues to interact with Excel. The following demonstrates a simple counter which increments stepwise. .. highlight:: c++ :: #include using namespace xloil; struct Counter : public RtdAsyncTask { Counter(int iStep) : _iStep(iStep) {} int _iStep; // This function arranges for the work to be done in the background, but must // exit when the `RtdNotifier` requests it std::future operator()(RtdNotifier notify) override { return std::async([=, step = _iStep]() { int _count = 0; while (!notify.cancelled()) { notify.publish(ExcelObj(_count)); std::this_thread::sleep_for(std::chrono::seconds(2)); _count += step; } }); } // When the cell which generated the task is recalculated, xlOil cannot tell // whether Excel is calling it with new inputs or RTD triggered the call to get // a result. It uses this equality operator to determine if it already has a // running task for the given set of inputs bool operator==(const IRtdAsyncTask& that_) const override { const auto* that = dynamic_cast(&that_); if (!that) return false; return _iStep == that->_iStep; } }; XLO_FUNC_START( rtdCounter(const ExcelObj& step) ) { auto value = rtdAsync( std::make_shared(step.toInt(1))); return returnValue(value ? *value : CellError::NA); } XLO_FUNC_END(rtdCounter); ================================================ FILE: docs/source/xlOil_Cpp/CustomGUI.rst ================================================ ============================== xlOil C++ GUI Customisation ============================== .. contents:: :local: xlOil allows dynamic creation of Excel Ribbon components and custom task panes. See :any:`concepts-ribbon` for background. The code differs slightly depending on whether you are writing a static XLL or an xlOil plugin. Creating a ribbon in a static XLL --------------------------------- .. highlight:: c++ :: void ribbonHandler(const RibbonControl& ctrl) {} struct MyAddin { std::shared_ptr theComAddin; MyAddin() { // We need this call so that xlOil defers execution of the ribbon creation // until Excel's COM interface is ready xllOpenComCall([this]() { auto mapper = [=](const wchar_t* name) mutable { return ribbonHandler; }; theComAddin = makeAddinWithRibbon( L"TestXlOil", LR"( ... )", mapper); }); } }; XLO_DECLARE_ADDIN(MyAddin); Creating a ribbon in an xlOil plugin ------------------------------------ .. highlight:: c++ :: #incude void ribbonHandler(const RibbonControl& ctrl) {} std::shared_ptr theComAddin; XLO_PLUGIN_INIT(xloil::AddinContext* ctx, const xloil::PluginContext& plugin) { xloil::linkLogger(ctx, plugin); auto mapper = [=](const wchar_t* name) mutable { return ribbonHandler; }; theComAddin = makeAddinWithRibbon( L"TestXlOil", LR"( ... )", mapper); return 0; } Creating a Custom Task Pane --------------------------- After a COM addin has been created using the ribbon examples above, a custom task pane can be added in the following way. .. highlight:: c++ :: std::shared_ptr taskPane(theComAddin->createTaskPane(L"xloil")); taskPane->setVisible(true); An option progid can be passed to `createTaskPane` to create a specific COM object as the root of the task pane, if omitted, xlOil uses a simple default object. ================================================ FILE: docs/source/xlOil_Cpp/DynamicRegistration.rst ================================================ ============================== xlOil C++ Dynamic Registration ============================== To support registration of functions in other languages, xlOil has the ability to register worksheet functions generated at runtime. In C++, you usually can create a static entry point for each function you want to register using xlOil macros. These macros end up calling `Excel12(xlfRegister, ...)` and pass the name of your function. Dynamic registration is a little trickier since `xlfRegister` requires the name of an entry point into your XLL rather than a function pointer. xlOil arranges for such a suitable entry point to exist by generating a small trampoline function in assembler which redirects to your function. However, this creates a tiny performance overhead (typically around 8-16 instructions increasing with the number of arguments). .. highlight:: c++ :: auto regId = RegisterLambda<>( [](const ExcelObj& arg1, const ExcelObj& arg2) { ... return returnValue(...); }) .name("Foobar") .help("Does nothing") .arg("Arg1") .registerFunc(); The lambda's can take as many `const ExcelObj&` args as required. It must return `ExcelObj*`, but it may throw: xlOil will return the error string. By specifying `const FuncInfo&` as the type of the first argument, the callable will be given a reference to the function registration info in addition the arguments passed by Excel. You can dynamically register any function you could register statically, so :any:`SpecialArgs` arguments are valid, as well as a void return, for example: .. highlight:: c++ :: auto regId = RegisterLambda( [](const FuncInfo&, const ExcelObj& arg1, const AsyncHandle& handle) { handle.returnValue(...); }) .name("AsyncFoobar").registerFunc(); The returned register Id is a `shared_ptr` whose destructor unregisters the function, so this must be kept in scope. ================================================ FILE: docs/source/xlOil_Cpp/Events.rst ================================================ ================ xlOil C++ Events ================ See :ref:`Events:Introduction`. Examples -------- .. highlight:: c++ :: // When the returned shared_ptr is destroyed, the handler is unhooked. auto ptr = xloil::Event::CalcCancelled().bind( [this]() { this->cancel(); })); // The returned id can be used to unhook the handler static auto id = Event::AfterCalculate() += [logger]() { logger->flush(); }; ================================================ FILE: docs/source/xlOil_Cpp/GettingStarted.rst ================================================ ========================= xlOil C++ Getting Started ========================= There are a few ways to build a C++ project with xlOil: 1. xlOil plugin: writing a DLL which can be loaded by xlOil after the loader XLL starts up the application. This allows co-operation with other xlOil features such as the the object cache and settings file. It means that cache references created by `xloRef` can be used in your addin. 2. XLL dynamically linking xlOil. This allows co-operation with the Excel object cache. Your XLL will need to be able to find xlOil when it loads - this may be easier if delay loading is employed. 3. XLL statically linking xlOil. This gives a standalone XLL. It does not include the xlOil cache functions `xloRef` and `xloVal`, although it is easy to define a private object cache with your own function names. The header files and `xlOil.lib` file required for dynamic linking are included in the `binary releases `_. You need: * `xlOil.lib`: linking library * `xlOil.dll`: main binary * `xlOil.xll`: XLL loader stub * `xlOil.ini`: Main settings file * `include/xloil`: header files The static libs are a lot larger, so you'll need to build them from source. If you want to read settings from the add-in's *.ini* file using *tomlplusplus* you can find this in `xlOil/external` - it's header-only. Creating an xlOil Plugin ~~~~~~~~~~~~~~~~~~~~~~~~ This method assumes you already have a running install of xlOil. Create a project & solution for your DLL. Building an xlOil plugin requires a number of build settings and the easiest way to get these right it is use a property sheet. First, include an edited copy of `./libs/SetRoot.props` where *xlOilRoot* points to the xlOil directory. Then include `./libs/xlOilPlugin.props`. Now add the following source file: .. highlight:: c++ :: #include XLO_PLUGIN_INIT(xloil::AddinContext* ctx, const xloil::PluginContext& plugin) { xloil::linkLogger(ctx, plugin); return 0; } using namespace xloil; XLO_FUNC_START( MyFunc(const ExcelObj* arg1, const ExcelObj* arg2) ) { auto result = arg1->toString() + ": " + arg2->toString(L"default value"); return ExcelObj::returnValue(result); } XLO_FUNC_END(MyFunc).threadsafe() .help(L"Joins two strings") .arg(L"Val1", L"First String") .arg(L"Val2", L"Second String"); Declaring an `XLO_PLUGIN_INIT` entry point lets xlOil know the DLL is a plugin. Calling `linkLogger` links the *spdlog* to the main xlOil log output. This is optional if you don't plan on using any logging functionality. The ``ExcelObj`` class wraps the xloper12 variant type used in the XLL interface. It provides a number of accessors with a focus on efficiency and avoidance of copying (see below). To load your plugin in Excel you need to ensure that the main xlOil add-in can locate your DLL. You can do this by changing the Addin enviroment in the `xlOil.ini` file or changing the current working directory or system PATH, or by simply ensuring your DLL is in the same directory as *xlOil.dll*. You should also add your plugin to the `Plugins=` key in `xlOil.ini`. Creating an XLL which statically links xlOil ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build xlOil with the *DebugStatic* or *ReleaseStatic* configuration. Create a project & solution for your XLL. It should output a DLL, but change the *Target Extension* to XLL. Building an xlOil-linked XLL requires a number of build settings and the easiest way to get these right it is use a property sheet. First, include an edited copy of `./libs/SetRoot.props` where *xlOilRoot* points to the xlOil directory. Then include `./libs/xlOilStaticLib.props`. Alternatively, you can copy and modify the `TestAddin` project, though you will still need to ensure *xlOilRoot* is set correctly. Create a `Main.cpp` file with the following contents: .. highlight:: c++ :: #include #include using namespace xloil; struct MyAddin {}; XLO_DECLARE_ADDIN(MyAddin); XLO_FUNC_START( MyFunc(const ExcelObj* arg1, const ExcelObj* arg2) ) { auto result = arg1->toString() + ": " + arg2->toString(L"default value"); return ExcelObj::returnValue(result); } XLO_FUNC_END(MyFunc).threadsafe() .help(L"Joins two strings") .arg(L"Val1", L"First String") .arg(L"Val2", L"Second String"); Debugging your addin ~~~~~~~~~~~~~~~~~~~~ Start a debugging session with the following settings where the XLL may be `xlOil.xll` or one you built yourself depending on which path you are following. * Command: * Arguments: Setting the working directory is optional, but it may help locate any externals DLLs your add-in uses. There are many examples to follow in the ``xloil_Utils`` and ``xloil_SQL`` projects. ================================================ FILE: docs/source/xlOil_Cpp/ObjectHandles.rst ================================================ ======================== xlOil C++ Object Handles ======================== It is often convenient to pass a pointer to an in-memory object from one function to another in a way which generalises :any:`core-cached-objects`. xlOil makes this very straightforward: .. highlight:: c++ :: XLO_FUNC_START( cacheOut(const ExcelObj& val) ) { auto key = makeCached(val.toInt()); return returnValue(key); } XLO_FUNC_END(cacheOut); XLO_FUNC_START( cacheIn(const ExcelObj& cacheKey) ) { auto* val = getCached(cacheKey.asPString()); return returnValue(val ? *val : 0); } XLO_FUNC_END(cacheIn); The cache key or handle returned looks `[Book1]Sheet1!R1C1,A`. The leading character allows rapid rejection of invalid cache strings; it is unique per cached object type. Cached objects do not persist when a spreadsheet is closed, but Excel does not know to recalculate the cells which generate the cache handles when a spreadsheet is opened, so a full recalc must be manually performed with Ctrl-Alt-F9. To avoid this requirement, you can make the functions which generate the handles RTD, but this carries some overhead. ================================================ FILE: docs/source/xlOil_Cpp/SpecialArgs.rst ================================================ ====================== XLL Special Arguments ====================== User-defined Excel functions in XLLs usually take a number of `const ExcelObj&` arguments and return a `ExcelObj*`. xlOil supports a number of other possibilities, the most useful of which are `RangeArg` and `AsyncHandle`. RangeArg -------- Declaring a class as `const RangeArg&` instead of an `ExcelObj` in registered function tells xlOil to allow range references to be passed. These are sheet addresses which avoid copying the underlying data. Normally, references to areas on the sheet are converted to arrays before being passed to the function. A `RangeArg` only allows the possibility that a range reference be specified, `RangeArg` inherits from `ExcelObj` and so may actually be any Excel type. AsyncHandle ----------- Declaring an argument as `const AsyncHandle&` causes the function to be declared as native async type to Excel. Such a function does not return a value directly but invokes the `returnValue` method on its handle to pass a value to Excel (which should only be done once). ExcelObj pointer ---------------- You may opt to receive a `const ExcelObj*` instead of a reference. This is purely preference and has no other impact. FPArray ------- An `FPArray` argument is a two-dimensional array of double. This is naturally very fast to access, particulary when combined with inplace returns but has a significant drawback: if if any value in the array passed to the function is not a number, Excel will return *#VALUE!* without actually invoking the function. Inplace returns ---------------- Excel supports returning values by modifying arguments in place. This is most useful for the FPArray type. To declare inplace return, simply make the argument a non-const ref or pointer. It is also possible to return an `ExcelObj` in-place but this is disabled by default. In the words of the XLL SDK: "Excel permits the registration of functions that return an XLOPER by modifying an argument in place. However, if an XLOPER argument points to memory, and the pointer is then overwritten by the return value of the DLL function, Excel can leak memory. If the DLL allocated memory for the return value, Excel might try to free that memory, which could cause an immediate crash. Therefore, you should not modify XLOPER/XLOPER12 arguments in place." In practice, it can be safe to modify an ExcelObj in place, for instance *xloSort* modifies its input by changing the row order in the array, but without changing memory allocation. However it does not use an inplace return because the drawback of this technique is that you cannot return a different type to the one passed, in particular you cannot return an error message or error type. To enable inplace `ExcelObj` returns, define the macro `XLOIL_UNSAFE_INPLACE_RETURN` before including any xlOil headers. ================================================ FILE: docs/source/xlOil_Cpp/StaticXLLs.rst ================================================ ====================== xlOil C++ Static XLLs ====================== As explained in :any:`GettingStarted`, it is possible to create an XLL which statically links xlOil. This allows an all-in-one XLL without worrying about DLL search paths. We expand on the example in :any:`GettingStarted` to illustrate some of the other static XLL features: .. highlight:: c++ :: #include #include using namespace xloil; struct MyAddin { MyAddin() { // This constructor is called by Excel's AutoOpen xllOpenComCall([this]() { // If we need to do some COM stuff on startup, like register a Ribbon, // it needs to go in this delayed COM callback. }); } ~MyAddin() { // This destructor is called by Excel's AutoClose } static wstring addInManagerInfo() { // The string returned here is displayed in Excel's addin options window // Note the function is static: it can be called before AutoOpen. return wstring(L"My Addin Name"); } }; XLO_DECLARE_ADDIN(MyAddin); ================================================ FILE: docs/source/xlOil_Cpp/index.rst ================================================ ====================== xlOil C++ ====================== xlOil C++ provides a layer above the `XLL API `_ and includes features not available via the XLL API, such as RTD Servers, Ribbon customisation and event handling. The xlOil C++ interface has a `doxygen API description <../../doxygen/html/doxygen/index.html>`_. .. toctree:: :maxdepth: 4 :caption: Contents GettingStarted SpecialArgs Events ObjectHandles CustomGUI CppRtd DynamicRegistration StaticXLLs COM Quick Tour of the Key Classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ExcelObj - An XLOPER wrapper ---------------------------- An *ExcelObj* is a friendly wrapper around Excel's *xloper12* struct which is the variant type used to pass arguments in the XLL interface. An *ExcelObj* it has the same size and *xloper12* so you can freely cast between the two (although be aware that *ExcelObj* has a destructor). *ExcelObj* supports a number of variant-like operations: it can be compared to strings or numbers, converted to a string, and created from values or initialiser lists. However, it's not recommended to use *ExcelObj* as a generic variant type - reserve it for interaction with the XLL API. ExcelArray / ArrayBuilder ------------------------- To **view** a suitable `ExcelObj` as an array, pass it to the `ExcelArray` constructor. By default, the array is 'trimmed' to last row and column containing data (i.e. not empty or N/A). `ExcelArray` is a view - it is lightweight but does not own the underlying data. The `ArrayBuilder` class constructs arrays to return to Excel. For efficiency (of the CPU not the programmer!) it requires you to know up-front how many array elements you require and the total length of all strings in the array. This may mean you need to make two passes of your data, but it saves iterating through the array on destruction. PString ------- Strings in the XLL API are Pascal wide char strings: the first character is the length and they are not null terminated. `PString` wraps such a string and provides a similar interface to `std::wstring`. There's also a `PStringView` type like `std::wstring_view`. Range / RangeArg / ExcelRef --------------------------- There are few types of range class with slightly different internals and usages. All behave in a "range-like" way, supporting: * `(i, j)` - accessor to pick out individual elements * `range(fromRow, fromCol, toRow, toCol)` - create a sub-range * `value()` - convert to an `ExcelObj` single value or array * `address()` - fetch the sheet address as a string * `set(val)` - sets the range values to the given `ExcelObj` (which may be an array) The `RangeArg` type should only be used to declare an argument to a user-defined worksheet function. Specifing this type means that xlOil is allowed to pass range references to your function. See :any:`SpecialArgs`. A `RangeArg` is not directly constructable - you should convert it to an `ExcelRef` to copy and pass it around. The `ExcelRef` type is equivalent to an *xltypeRef* `ExcelObj`, that is it points to a rectangular space on a specific sheet. It can be created from an `ExcelObj` which is a range reference (typically via `RangeArg.toExcelRef()`) or a sheet address string. The `Range` type is a virtual base: it may point either to an `ExcelRef` range or a COM range object. Prefer `ExcelRef` when you are sure the range information has been passed via an XLL worksheet function. callExcel - calling XLL API functions ------------------------------------- The XLL SDK docs describe a number of API calls beginning with `xl` such as `xlCoerce` and `xlSheetNm`. There are also many more poorly documented API calls beginning with `xlc` and `xlf`. They can all be found in `xlcall.h`. Invoking these functions is straightforward: .. highlight:: c++ :: ExcelObj result = callExcel(msxll::xlSheetId, "Sheet1"); All arguments are automatically converted to `ExcelObj` type where a conversion is possible before being passed to Excel. The type of the return value is given in the documentation. runExcelThread - calling blocked API functions ---------------------------------------------- Excel's COM API must be called on the main thread, since it's single-threaded apartment. Breaking this rule may lead to undefined behaviour. In addition, the COM interface must be 'ready' which means no dialog boxes are open or other tasks which busy the COM interface. Calls to the XLL API generally also require the main thread (with a few exceptions) and being in the correct 'context', that is being in a function invoked by Excel. The `runExcelThread` function queues a message to a hidden window or an asynchronous procedure call (APC) callback which will be executed on the main thread when Excel passes control to windows or pumps its message loop respectively (a window message is the default, most responsive choice). The `runExcelThread` function can retry calls, trying to wait until the COM interface is ready (sadly there is no mechanism to allow Excel to inform applications when it is ready to work, you just have to keep trying). In addition, setting the `XLL_API` flag runs the callback in the XLL context. ================================================ FILE: docs/source/xlOil_Python/BuiltInUDFs.rst ================================================ ================================== xlOil Built-in Worksheet Functions ================================== The the important ``xloRef`` and ``xloVal`` functions are described at :ref:`core-cached-objects` xloPyLoad: import and scan a python module (worksheet function) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. function:: xloImport(ModuleName:str, From=None, As=None) Loads functions from a module and registers them in Excel. The functions do not have to be decorated. Provides an analogue of `from X import Y as Z` ModuleName: A module name or a full path name to a target py file. If empty, the workbook module with the same name as the calling workbook is (re)loaded. From: If omitted, imports the specified module as normal, i.e. :any:`xloil.scan_module` If a value or array, registers only the specified object names. If "*", all objects are registered. To: Optionally specifies the Excel function names to register in the same order as `From`. Should have the same length as `From`. See :any:`xloil.import_functions` .. function:: xloAttr(Object, Name:str, *Args, **Kwargs) Returns the named attribute value, or the result of calling it if possible. ie, ``object.attr`` or ``object.attr(*args, *kwargs)`` if it is a callable. Object: The target object Name: The name of the attribute to be returned. The attribute can be a bound method, member, property, method, function or class Args If the attribute is callable, it will be called using these positional arguments Kwargs If the attribute is callable, it will be called using these keyword arguments .. function:: xloAttrObj(Object, Name:str, *Args, **Kwargs) Returns the value of named attribute or the result of calling the attribute. Behaves like `xloAttr` except always returns a cache object (see :ref:`xlOil_Python/TypeConversion:Cached Objects`). This function is useful to stop the default conversion to Excel, for example when returning an iterable. A typical use is when chaining `=xloAttrObj` calls. .. function:: xloPyDebug(Debugger) See :ref:`xlOil_Python/Debugging:Selecting the debugger programmatically` ================================================ FILE: docs/source/xlOil_Python/Concepts.rst ================================================ ========================= xlOil Python Concepts ========================= .. contents:: :local: Workbook Modules ---------------- When an Excel workbook is opened, xlOil tries to load the module `.py` (this is configurable). When registering functions from workbook modules, xlOil defaults to making any declared functions :ref:`xlOil_Python/Functions:Local Functions` The function :any:`xloil.linked_workbook` when called from a workbook module retrieves the associated workbook path. Array Functions --------------- By default, xlOil-Python converts Excel array arguments to numpy arrays. The conversion happens entirely in C++ and is very fast. Where possible you should write functions which support array processing (vectorising) to take advantage of this, for example the following works when it's arguments are arrays or numbers: :: @xlo.func def cubic(x, a, b, c, d) return a * x ** 3 + b * x ** 2 + c * x + d You can take this further to evaluate polynominals of n-th degree: :: @xlo.func def poly(x, coeffs: xlo.Array(float)): return np.sum(coeffs * x[:,None] ** range(coeffs.T.shape[0]), axis=1) Specifing that we expect an array argument and the data type of that array avoids the overhead of xlOil determining the type. There is a problem with this function: what happens if ``x`` is two-dimensional? To avoid this possibility we can specify: :: @xlo.func def poly(x: xlo.Array(dims=1), coeffs: xlo.Array(float)): return np.sum(coeffs * X[:,None] ** range(coeffs.T.shape[0]), axis=1) Events ------ Events allow for a callback on user interaction. If you are familiar with VBA, you may have used Excel's event model already. Most of the workbook events described in `Excel.Appliction `_ are available in xlOil. See :ref:`xlOil_Python/ModuleReference:Events` for more details on python events and :doc:`Events` for a description of the available Excel events. Excel events do not use return values. However, some events take reference parameters. For example, `WorkbookBeforeSave` has a boolean `cancel` parameter. Setting this to True cancels the save. As references to primitive types aren't supported in python, in xlOil you need to set this value using `cancel.value=True`. Event handlers are (currently) global to the Excel instance, so you may need to filter by workbook name when handling events even if you have hooked the event in a local workbook module. Examples ~~~~~~~~ :: def greet(workbook, worksheet): xlo.Range(f"[{workbook}]{worksheet}!A1") = "Hello!" xlo.event.WorkbookNewSheet += greet Registering functions in other modules -------------------------------------- xlOil automatically scans modules when they are imported or reloaded via a hook in python's import mechanism. This ensures any :any:`xloil.func` decorated functions are registered. If you load a module outside the normal ``import`` mechanism, you can tell xlOil to look for functions to register with :any:`xloil.scan_module`. Also see :any:`xlOil_Python/Functions:Dynamic Registration`, which explains how any python callable can be registered as an Excel function. Multiple addins and event loops ------------------------------- *xlOil_Python* can be used by multiple add-ins, that is, more than one XLL loader with its own settings and python codebase can exist in the same Excel session. * Each add-in / XLL is loaded in a background thread equipped with an `asyncio` event loop. Get the loop using :any:`xloil.get_event_loop`. * You can find the addin associated with the currently running code with :any:`xloil.source_addin` . * All add-ins share the same python interpreter * All add-ins share the python object cache * Worksheet functions are executed in Excel's main thread or one of its worker threads for thread safe functions * Async / RTD worksheet functions are executed in a dedicated xlOil Core event loop which you can access with ``xloil.get_async_loop()`` * You can ask xlOil to create a separate thread & event loop for an addin. Although CPython supports subinterpreters, most C-based extensions, particularly *numpy* do not, so there are no plans to add subinterpreter support at this stage. ================================================ FILE: docs/source/xlOil_Python/CustomGUI.rst ================================================ ============================== xlOil Python GUI Customisation ============================== .. contents:: :local: Status Bar ---------- Possibly the simplest Excel GUI interaction: writing messages to Excel's status bar: :: from xloil import StatusBar with StatusBar(1000) as status: status.msg('Doing slow thing') ... status.msg('Done slow thing') The :any:`xloil.StatusBar` object clears the status bar after the specified number of milliseconds once the `with` context ends Ribbon ------ xlOil allows dynamic creation of Excel Fluent Ribbon components. See :any:`concepts-ribbon` for background. :: gui = xlo.ExcelGUI(r'''....''', funcmap={ 'onClick1': run_click, 'onClick2': run_click, 'onUpdate': update_text, }) The ``gui`` object :any:`xloil.ExcelGUI` holds a handle to a COM addin created to support the ribbon customisation. If the object goes out of scope and is deleted by python or if you call ``gui.disconnect()``, the add-in is unloaded along with the ribbon customisation and any custom task panes. The ``funcmap`` dictionary (or function) links callbacks named in the ribbon XML to python functions. Each handler should have a signature like the following: :: def ribbon_button_press(ctrl: RibbonControl) ... def ribbon_callback2(ctrl: RibbonControl, arg1, arg2) ... def ribbon_get_text_label(ctrl: RibbonControl, *args) return "something" async def ribbon_callback4(ctrl: RibbonControl, *args) ... The ``ctrl`` argument points to the control which raised the callback. The number of additional arguments is callback dependent. Some callbacks are expected to return a value. See `Customizing the Office Fluent Ribbon `_ for a description of the appropriate callback signature. .. note:: Callbacks are executed in Excel's main thread unless declared *async*, in which case they will be executed in the addin's event loop. Async callbacks cannot return values. Instead of a dictionary, the `funcmap` object can be a function which takes any string and returns a callback handler. See :doc:`ExampleGUI` for an example of ribbon customisation, or look at xlOil's own ribbon in the `xloil/xloil_ribbon.py` module. Setting button images ===================== Any `getImage` callbacks must return a `PIL Image `_. This is converted to an appropriate COM object by xlOil. Instead of using a separate `getImage` callback per control, a single `loadImage` attribute can be added: :: ...