diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /pyuno | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pyuno')
68 files changed, 11673 insertions, 0 deletions
diff --git a/pyuno/CustomTarget_python_shell.mk b/pyuno/CustomTarget_python_shell.mk new file mode 100644 index 0000000000..541620304d --- /dev/null +++ b/pyuno/CustomTarget_python_shell.mk @@ -0,0 +1,40 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,pyuno/python_shell)) + +$(eval $(call gb_CustomTarget_register_targets,pyuno/python_shell,\ + os.sh \ + python.sh \ +)) + +ifeq ($(OS),MACOSX) +pyuno_PYTHON_SHELL_VERSION:=$(PYTHON_VERSION_MAJOR).$(PYTHON_VERSION_MINOR) +else +pyuno_PYTHON_SHELL_VERSION:=$(PYTHON_VERSION) +endif + +$(call gb_CustomTarget_get_workdir,pyuno/python_shell)/python.sh : \ + $(SRCDIR)/pyuno/zipcore/python.sh \ + $(call gb_CustomTarget_get_workdir,pyuno/python_shell)/os.sh + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),CAT,1) + $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),CAT) + cat $^ > $@ && chmod +x $@ + $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),CAT) + +$(call gb_CustomTarget_get_workdir,pyuno/python_shell)/os.sh : \ + $(SRCDIR)/pyuno/zipcore/$(if $(filter MACOSX,$(OS)),mac,nonmac).sh \ + $(BUILDDIR)/config_$(gb_Side)/config_python.h + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),SED,1) + $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),SED) + sed -e "s/%%PYVERSION%%/$(pyuno_PYTHON_SHELL_VERSION)/g" \ + $< > $@ + $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),SED) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/CustomTarget_pyuno_pythonloader_ini.mk b/pyuno/CustomTarget_pyuno_pythonloader_ini.mk new file mode 100644 index 0000000000..cc5a67f3ca --- /dev/null +++ b/pyuno/CustomTarget_pyuno_pythonloader_ini.mk @@ -0,0 +1,38 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CustomTarget_CustomTarget,pyuno/pythonloader_ini)) + +$(eval $(call gb_CustomTarget_register_targets,pyuno/pythonloader_ini, \ + $(call gb_Helper_get_rcfile,pythonloader.uno) \ +)) + +$(call gb_CustomTarget_get_workdir,pyuno/pythonloader_ini)/$(call gb_Helper_get_rcfile,pythonloader.uno): \ + $(BUILDDIR)/config_$(gb_Side)/config_python.h \ + $(SRCDIR)/pyuno/CustomTarget_pyuno_pythonloader_ini.mk + $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),ECH,1) + $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),ECH) + ( printf '[Bootstrap]\n' && \ + $(if $(SYSTEM_PYTHON),, \ + printf 'PYUNO_LOADER_PYTHONHOME=%s\n' \ + $(if $(filter MACOSX,$(OS)), \ + '$$ORIGIN/../Frameworks/LibreOfficePython.framework', \ + '$$ORIGIN/python-core-$(PYTHON_VERSION)') &&) \ + printf 'PYUNO_LOADER_PYTHONPATH=%s$$ORIGIN\n' \ + $(if $(SYSTEM_PYTHON), \ + '', \ + $(if $(filter MACOSX,$(OS)), \ + '$(foreach dir,/ /lib-dynload /lib-tk /site-packages,$(patsubst %/,%,$$ORIGIN/../Frameworks/LibreOfficePython.framework/Versions/Current/lib/python$(PYTHON_VERSION_MAJOR).$(PYTHON_VERSION_MINOR)$(dir))) ', \ + $(if $(filter WNT,$(OS)), \ + '$(foreach dir,/ /site-packages,$(patsubst %/,%,$$ORIGIN/python-core-$(PYTHON_VERSION)/lib$(dir))) ', \ + '$(foreach dir,/ /lib-dynload /lib-tk /site-packages,$(patsubst %/,%,$$ORIGIN/python-core-$(PYTHON_VERSION)/lib$(dir))) '))) \ + ) > $@ + $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),ECH) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/Executable_python.mk b/pyuno/Executable_python.mk new file mode 100644 index 0000000000..192d54cf9e --- /dev/null +++ b/pyuno/Executable_python.mk @@ -0,0 +1,22 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Executable_Executable,python)) + +$(eval $(call gb_Executable_use_static_libraries,python,\ + ooopathutils \ +)) + +$(eval $(call gb_Executable_add_exception_objects,python,\ + pyuno/zipcore/python \ +)) + +$(eval $(call gb_Executable_add_default_nativeres,python)) + +# vim:set noet sw=4 ts=4: diff --git a/pyuno/IwyuFilter_pyuno.yaml b/pyuno/IwyuFilter_pyuno.yaml new file mode 100644 index 0000000000..6e3a5df38b --- /dev/null +++ b/pyuno/IwyuFilter_pyuno.yaml @@ -0,0 +1,37 @@ +--- +assumeFilename: pyuno/source/module/pyuno.cxx +excludelist: + pyuno/inc/pyuno.hxx: + # Used in #ifdef + - Python.h + pyuno/source/module/pyuno_impl.hxx: + # Used in #ifdef + - Python.h + # Base class needs complete type + - com/sun/star/lang/XUnoTunnel.hpp + - com/sun/star/script/XInvocation.hpp + # Needed for typedef + - com/sun/star/container/XIndexAccess.hpp + - cppuhelper/weakref.hxx + pyuno/source/loader/pyuno_loader.cxx: + # Don't replace with URE impl. detail + - osl/file.hxx + # Needed for direct member access + - com/sun/star/uno/XComponentContext.hpp + # Needed on WIN32 + - o3tl/char16_t2wchar_t.hxx + pyuno/source/module/pyuno_module.cxx: + # Needed on MACOSX + - config_folders.h + # Don't replace with URE impl. detail + - rtl/bootstrap.hxx + pyuno/source/module/pyuno_runtime.cxx: + # Needed on MACOSX + - config_folders.h + # Needed for direct member access + - com/sun/star/container/XHierarchicalNameAccess.hpp + # Needed for template + - com/sun/star/script/XInvocation2.hpp + pyuno/zipcore/python.cxx: + # Needed on WIN32 + - tools/pathutils.hxx diff --git a/pyuno/Library_pythonloader.mk b/pyuno/Library_pythonloader.mk new file mode 100644 index 0000000000..4e3776b848 --- /dev/null +++ b/pyuno/Library_pythonloader.mk @@ -0,0 +1,40 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,pythonloader)) + +$(eval $(call gb_Library_set_componentfile,pythonloader,pyuno/source/loader/pythonloader,pyuno)) + +$(eval $(call gb_Library_set_include,pythonloader,\ + -I$(SRCDIR)/pyuno/source/loader \ + -I$(SRCDIR)/pyuno/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Library_use_api,pythonloader,\ + udkapi \ +)) + +$(eval $(call gb_Library_use_libraries,pythonloader,\ + cppu \ + cppuhelper \ + pyuno \ + sal \ +)) + +$(eval $(call gb_Library_use_externals,pythonloader,\ + boost_headers \ + python \ +)) + +$(eval $(call gb_Library_add_exception_objects,pythonloader,\ + pyuno/source/loader/pyuno_loader \ +)) + +# vim:set noet sw=4 ts=4: diff --git a/pyuno/Library_pyuno.mk b/pyuno/Library_pyuno.mk new file mode 100644 index 0000000000..49c5b9104e --- /dev/null +++ b/pyuno/Library_pyuno.mk @@ -0,0 +1,52 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,pyuno)) + +$(eval $(call gb_Library_set_include,pyuno,\ + -I$(SRCDIR)/pyuno/source/loader \ + -I$(SRCDIR)/pyuno/inc \ + $$(INCLUDE) \ +)) + +$(eval $(call gb_Library_add_defs,pyuno,\ + -DLO_DLLIMPLEMENTATION_PYUNO \ +)) + +$(eval $(call gb_Library_use_api,pyuno,\ + udkapi \ +)) + +$(eval $(call gb_Library_use_libraries,pyuno,\ + cppu \ + cppuhelper \ + sal \ + salhelper \ +)) + +$(eval $(call gb_Library_use_externals,pyuno,\ + boost_headers \ + python \ +)) + +$(eval $(call gb_Library_add_exception_objects,pyuno,\ + pyuno/source/module/pyuno_runtime \ + pyuno/source/module/pyuno \ + pyuno/source/module/pyuno_struct \ + pyuno/source/module/pyuno_callable \ + pyuno/source/module/pyuno_module \ + pyuno/source/module/pyuno_type \ + pyuno/source/module/pyuno_util \ + pyuno/source/module/pyuno_except \ + pyuno/source/module/pyuno_adapter \ + pyuno/source/module/pyuno_gc \ + pyuno/source/module/pyuno_iterator \ +)) + +# vim:set noet sw=4 ts=4: diff --git a/pyuno/Library_pyuno_wrapper.mk b/pyuno/Library_pyuno_wrapper.mk new file mode 100644 index 0000000000..4bce8fc00b --- /dev/null +++ b/pyuno/Library_pyuno_wrapper.mk @@ -0,0 +1,48 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Library_Library,pyuno_wrapper)) + +$(eval $(call gb_Library_set_include,pyuno_wrapper,\ + -I$(SRCDIR)/pyuno/source/module \ + -I$(SRCDIR)/pyuno/inc \ + $$(INCLUDE) \ +)) + +# not using external "python" because we do not want to link against python +$(eval $(call gb_Library_use_externals,pyuno_wrapper,\ + python_headers \ +)) + +# python "import pyuno" dlopens pyuno.so as RTLD_LOCAL, so g++ exception +# handling used to not work, so pyuno.so (pyuno_wrapper) is just a thin wrapper +# that dlopens libpyuno.so as RTLD_GLOBAL; but when pyuno.so wrapper links +# against libstdc++ (which has not previously been loaded into python process), +# that resolves its _ZNSs4_Rep20_S_empty_rep_storageE to itself, but later LO +# libs (loaded though RTLD_GLOBAL libpyuno.so) may resolve that symbol to e.g. +# cppu, because they happen to see that before libstdc++; so the requirement has +# always been that RTLD_LOCAL-loaded pyuno.so wrapper implicitly load into the +# process as little as possible. +# To ensure that pyuno.so does not link against libstdc++ the dynamic link +# command invokes gcc and not g++ if there are only C objects, so +# don't add any C++ objects here! + +ifeq ($(filter DRAGONFLY FREEBSD NETBSD OPENBSD MACOSX,$(OS)),) + +$(eval $(call gb_Library_add_libs,pyuno_wrapper,\ + -ldl \ +)) + +endif + +$(eval $(call gb_Library_add_cobjects,pyuno_wrapper,\ + pyuno/source/module/pyuno_dlopenwrapper \ +)) + +# vim:set noet sw=4 ts=4: diff --git a/pyuno/Makefile b/pyuno/Makefile new file mode 100644 index 0000000000..0997e62848 --- /dev/null +++ b/pyuno/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/Module_pyuno.mk b/pyuno/Module_pyuno.mk new file mode 100644 index 0000000000..71d7b48e8b --- /dev/null +++ b/pyuno/Module_pyuno.mk @@ -0,0 +1,62 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,pyuno)) + +ifneq ($(DISABLE_PYTHON),TRUE) + +$(eval $(call gb_Module_add_targets,pyuno,\ + CustomTarget_pyuno_pythonloader_ini \ + Library_pyuno \ + Library_pythonloader \ + Package_python_scripts \ + Package_pyuno_pythonloader_ini \ + Rdb_pyuno \ +)) + +ifneq ($(OS),WNT) +$(eval $(call gb_Module_add_targets,pyuno,\ + Library_pyuno_wrapper \ +)) +endif + +# Windows: only --enable-python=internal possible +# python-core: pyuno/python.exe on Windows +ifeq ($(OS),WNT) +$(eval $(call gb_Module_add_targets,pyuno,\ + Executable_python \ +)) +endif + +ifeq ($(SYSTEM_PYTHON),) + +# python-core: python.sh on Unix +ifneq ($(OS),WNT) +$(eval $(call gb_Module_add_targets,pyuno,\ + CustomTarget_python_shell \ + Package_python_shell \ +)) +endif + +endif # SYSTEM_PYTHON + +ifneq ($(OS),MACOSX) +$(eval $(call gb_Module_add_check_targets,pyuno, \ + PythonTest_pyuno_pytests_testssl \ +)) +endif + +$(eval $(call gb_Module_add_subsequentcheck_targets,pyuno, \ + PythonTest_pyuno_pytests_testcollections \ + PythonTest_pyuno_pytests_insertremovecells \ +)) + +endif # DISABLE_PYTHON + +# vim:set noet sw=4 ts=4: diff --git a/pyuno/Package_python_scripts.mk b/pyuno/Package_python_scripts.mk new file mode 100644 index 0000000000..0588af3705 --- /dev/null +++ b/pyuno/Package_python_scripts.mk @@ -0,0 +1,19 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Package_Package,pyuno_python_scripts,$(SRCDIR)/pyuno/source)) + +$(eval $(call gb_Package_add_files,pyuno_python_scripts,$(LIBO_LIB_PYUNO_FOLDER),\ + loader/pythonloader.py \ + module/uno.py \ + module/unohelper.py \ + officehelper.py \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/Package_python_shell.mk b/pyuno/Package_python_shell.mk new file mode 100644 index 0000000000..f75cda39af --- /dev/null +++ b/pyuno/Package_python_shell.mk @@ -0,0 +1,18 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Package_Package,python_shell,$(call gb_CustomTarget_get_workdir,pyuno/python_shell))) + +ifeq ($(OS),MACOSX) +$(eval $(call gb_Package_add_file,python_shell,$(LIBO_ETC_FOLDER)/python,python.sh)) +else +$(eval $(call gb_Package_add_file,python_shell,$(LIBO_BIN_FOLDER)/python,python.sh)) +endif + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/Package_pyuno_pythonloader_ini.mk b/pyuno/Package_pyuno_pythonloader_ini.mk new file mode 100644 index 0000000000..0fae146186 --- /dev/null +++ b/pyuno/Package_pyuno_pythonloader_ini.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Package_Package,pyuno_pythonloader_ini,$(call gb_CustomTarget_get_workdir,pyuno/pythonloader_ini))) + +$(eval $(call gb_Package_add_files,pyuno_pythonloader_ini,$(LIBO_ETC_FOLDER), \ + $(call gb_Helper_get_rcfile,pythonloader.uno) \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/PythonTest_pyuno_pytests_insertremovecells.mk b/pyuno/PythonTest_pyuno_pytests_insertremovecells.mk new file mode 100644 index 0000000000..3b3e3c0988 --- /dev/null +++ b/pyuno/PythonTest_pyuno_pytests_insertremovecells.mk @@ -0,0 +1,20 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_PythonTest_PythonTest,pyuno_pytests_insertremovecells)) + +$(eval $(call gb_PythonTest_set_defs,pyuno_pytests_insertremovecells,\ + TDOC="$(SRCDIR)/pyuno/qa/pytests/testdocuments" \ +)) + +$(eval $(call gb_PythonTest_add_modules,pyuno_pytests_insertremovecells,$(SRCDIR)/pyuno/qa/pytests,\ + insertremovecells \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/PythonTest_pyuno_pytests_testcollections.mk b/pyuno/PythonTest_pyuno_pytests_testcollections.mk new file mode 100644 index 0000000000..abd26699c2 --- /dev/null +++ b/pyuno/PythonTest_pyuno_pytests_testcollections.mk @@ -0,0 +1,27 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_PythonTest_PythonTest,pyuno_pytests_testcollections)) + +$(eval $(call gb_PythonTest_add_modules,pyuno_pytests_testcollections,$(SRCDIR)/pyuno/qa/pytests,\ + testcollections_XIndexAccess \ + testcollections_XIndexReplace \ + testcollections_XIndexContainer \ + testcollections_XNameAccess \ + testcollections_XNameReplace \ + testcollections_XNameContainer \ + testcollections_XEnumerationAccess \ + testcollections_XEnumeration \ + testcollections_XCellRange \ + testcollections_mixednameindex \ + testcollections_misc \ + testcollections_misc2 \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/PythonTest_pyuno_pytests_testssl.mk b/pyuno/PythonTest_pyuno_pytests_testssl.mk new file mode 100644 index 0000000000..e431af31a2 --- /dev/null +++ b/pyuno/PythonTest_pyuno_pytests_testssl.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_PythonTest_PythonTest,pyuno_pytests_testssl)) + +$(eval $(call gb_PythonTest_add_modules,pyuno_pytests_testssl,$(SRCDIR)/pyuno/qa/pytests,\ + testssl \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/README.md b/pyuno/README.md new file mode 100644 index 0000000000..95e4877067 --- /dev/null +++ b/pyuno/README.md @@ -0,0 +1,22 @@ +# Python UNO Bindings + +UNO bindings for the Python programming language. + +To have much joy debugging Python extensions you need to: + ++ a) edit `pythonloader.py` in your install setting `DEBUG=1` at the top ++ b) `touch pyuno/source/module/pyuno_runtime.cxx` and `make debug=true` in `pyuno` + +Then you'll start to see your exceptions on the console instead of them getting +lost at the UNO interface. + +Python also comes with a gdb script +`libpython$(PYTHON_VERSION_MAJOR).$(PYTHON_VERSION_MINOR)m.so.1.0-gdb.py` +that is copied to `instdir` and will be auto-loaded by `gdb`; +it provides commands like `py-bt` to get a Python-level backtrace, +and `py-print` to print Python variables. + +Another way to debug Python code is to use `pdb`: edit some initialization +function to insert `import pdb; pdb.set_trace()` (somewhere so that it is +executed early), then run `soffice` from a terminal and a command-line Python +debugger will appear where you can set Python-level breakpoints. diff --git a/pyuno/Rdb_pyuno.mk b/pyuno/Rdb_pyuno.mk new file mode 100644 index 0000000000..f4e3fc7a79 --- /dev/null +++ b/pyuno/Rdb_pyuno.mk @@ -0,0 +1,12 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Rdb_Rdb_install,pyuno)) + +# vim: set noet sw=4 ts=4: diff --git a/pyuno/demo/Addons.xcu b/pyuno/demo/Addons.xcu new file mode 100644 index 0000000000..59d2d18563 --- /dev/null +++ b/pyuno/demo/Addons.xcu @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + --> + +<oor:node xmlns:oor="http://openoffice.org/2001/registry" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + oor:name="Addons" oor:package="org.openoffice.Office"> +<node oor:name="AddonUI"> + <node oor:name="AddonMenu"> + <node oor:name="org.openoffice.comp.pyuno.demo.HelloWorld" oor:op="replace"> + <prop oor:name="URL" oor:type="xs:string"> + <value>service:org.openoffice.comp.pyuno.demo.HelloWorld?insert</value> + </prop> + <prop oor:name="Title" oor:type="xs:string"> + <value xml:lang="x-no-translate">Insert Hello World</value> + <value xml:lang="en-US">Insert Hello World</value> + </prop> + </node> + </node> +</node> +</oor:node> diff --git a/pyuno/demo/biblioaccess.py b/pyuno/demo/biblioaccess.py new file mode 100644 index 0000000000..0e5d0b4c80 --- /dev/null +++ b/pyuno/demo/biblioaccess.py @@ -0,0 +1,53 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +import uno +from com.sun.star.sdb.CommandType import COMMAND + +def main(): + connectionString = "socket,host=localhost,port=2002" + + url = "uno:" + connectionString + ";urp;StarOffice.ComponentContext" + + localCtx = uno.getComponentContext() + localSmgr = localCtx.ServiceManager + resolver = localSmgr.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", localCtx) + ctx = resolver.resolve(url) + smgr = ctx.ServiceManager + + rowset =smgr.createInstanceWithContext("com.sun.star.sdb.RowSet", ctx) + rowset.DataSourceName = "Bibliography" + rowset.CommandType = COMMAND + rowset.Command = "SELECT IDENTIFIER, AUTHOR FROM biblio" + + rowset.execute(); + + print("Identifier\tAuthor") + + id = rowset.findColumn("IDENTIFIER") + author = rowset.findColumn("AUTHOR") + while rowset.next(): + print(rowset.getString(id) + "\t" + repr(rowset.getString(author))) + + rowset.dispose(); + +main() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/hello_world_comp.py b/pyuno/demo/hello_world_comp.py new file mode 100644 index 0000000000..020953b4f4 --- /dev/null +++ b/pyuno/demo/hello_world_comp.py @@ -0,0 +1,59 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +import unohelper + +from com.sun.star.task import XJobExecutor + +# implement a UNO component by deriving from the standard unohelper.Base class +# and from the interface(s) you want to implement. +class HelloWorldJob(unohelper.Base, XJobExecutor): + def __init__(self, ctx): + # store the component context for later use + self.ctx = ctx + + def trigger(self, args): + # note: args[0] == "HelloWorld", see below config settings + + # retrieve the desktop object + desktop = self.ctx.ServiceManager.createInstanceWithContext( + "com.sun.star.frame.Desktop", self.ctx) + + # get current document model + model = desktop.getCurrentComponent() + + # access the document's text property + text = model.Text + + # create a cursor + cursor = text.createTextCursor() + + # insert the text into the document + text.insertString(cursor, "Hello World", 0) + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() + +g_ImplementationHelper.addImplementation( \ + HelloWorldJob, # UNO object class + "org.openoffice.comp.pyuno.demo.HelloWorld", # implementation name + ("com.sun.star.task.Job",),) # list of implemented services + # (the only service) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/makefile.mk b/pyuno/demo/makefile.mk new file mode 100644 index 0000000000..11fe66f31b --- /dev/null +++ b/pyuno/demo/makefile.mk @@ -0,0 +1,60 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +PRJNAME=pyuno +PRJ=.. + +.INCLUDE : settings.mk +.INCLUDE : pyversion.mk + +ROOT=$(MISC)/pyuno-doc + +FILES=\ + $(ROOT)/python-bridge.html \ + $(ROOT)/customized_setup.png \ + $(ROOT)/mode_component.png \ + $(ROOT)/mode_ipc.png \ + $(ROOT)/modes.sxd \ + $(ROOT)/optional_components.png \ + $(ROOT)/samples/swriter.py \ + $(ROOT)/samples/swritercomp.py \ + $(ROOT)/samples/ooextract.py \ + $(ROOT)/samples/biblioaccess.py \ + $(ROOT)/samples/swritercompclient.py \ + $(ROOT)/samples/hello_world_pyuno.zip + + +$(MISC)/pyuno-doc.zip : dirs $(FILES) + -rm -f $@ + cd $(MISC) && zip -r pyuno-doc.zip pyuno-doc + +dirs .PHONY : + -mkdir $(ROOT) + -mkdir $(ROOT)/samples + +$(ROOT)/samples/hello_world_pyuno.zip : hello_world_comp.py Addons.xcu + -rm -f $@ + zip $@ hello_world_comp.py Addons.xcu + +$(ROOT)/samples/% : % + -rm -f $@ + $(COPY) $? $@ + +$(ROOT)/% : ../doc/% + -rm -f $@ + $(COPY) $? $@ diff --git a/pyuno/demo/ooextract.py b/pyuno/demo/ooextract.py new file mode 100644 index 0000000000..a5699548ce --- /dev/null +++ b/pyuno/demo/ooextract.py @@ -0,0 +1,128 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +import getopt,sys +import uno + +from unohelper import Base,systemPathToFileUrl +from os import getcwd +from com.sun.star.beans import PropertyValue +from com.sun.star.uno import Exception as UnoException +from com.sun.star.io import IOException, XOutputStream + +class OutputStream(Base, XOutputStream): + def __init__(self): + self.closed = 0 + + def closeOutput(self): + self.closed = 1 + + def writeBytes(self, seq): + sys.stdout.write(seq.value) + + def flush(self): + pass + +def main(): + retVal = 0 + doc = None + + try: + opts, args = getopt.getopt(sys.argv[1:], "hc:", ["help", "connection-string=", "html"]) + format = None + url = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" + filterName = "Text (Encoded)" + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + if o in ("-c", "--connection-string"): + url = "uno:" + a + ";urp;StarOffice.ComponentContext" + if o == "--html": + filterName = "HTML (StarWriter)" + + print(filterName) + if not len(args): + usage() + sys.exit() + + ctxLocal = uno.getComponentContext() + smgrLocal = ctxLocal.ServiceManager + + resolver = smgrLocal.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", ctxLocal) + ctx = resolver.resolve(url) + smgr = ctx.ServiceManager + + desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx) + + cwd = systemPathToFileUrl(getcwd()) + outProps = ( + PropertyValue("FilterName" , 0, filterName, 0), + PropertyValue("OutputStream", 0, OutputStream(), 0)) + inProps = PropertyValue("Hidden", 0 , True, 0), + for path in args: + try: + fileUrl = uno.absolutize(cwd, systemPathToFileUrl(path)) + doc = desktop.loadComponentFromURL(fileUrl , "_blank", 0, inProps) + + if not doc: + raise UnoException("Could not open stream for unknown reason", None) + + doc.storeToURL("private:stream", outProps) + except IOException as e: + sys.stderr.write("Error during conversion: " + e.Message + "\n") + retVal = 1 + except UnoException as e: + sys.stderr.write("Error (" + repr(e.__class__) + ") during conversion: " + e.Message + "\n") + retVal = 1 + if doc: + doc.dispose() + + except UnoException as e: + sys.stderr.write("Error (" + repr(e.__class__) + "): " + e.Message + "\n") + retVal = 1 + except getopt.GetoptError as e: + sys.stderr.write(str(e) + "\n") + usage() + retVal = 1 + + sys.exit(retVal) + +def usage(): + sys.stderr.write("usage: ooextract.py --help |\n"+ + " [-c <connection-string> | --connection-string=<connection-string>\n"+ + " file1 file2 ...\n"+ + "\n" + + "Extracts plain text from documents and prints it to stdout.\n" + + "Requires an OpenOffice.org instance to be running. The script and the\n"+ + "running OpenOffice.org instance must be able to access the file with\n"+ + "by the same system path.\n" + "\n"+ + "-c <connection-string> | --connection-string=<connection-string>\n" + + " The connection-string part of a UNO URL to where the\n" + + " the script should connect to in order to do the conversion.\n" + + " The strings defaults to socket,host=localhost,port=2002\n" + "--html \n" + " Instead of the text filter, the writer html filter is used\n" + ) + +main() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/pyunoenv.bat b/pyuno/demo/pyunoenv.bat new file mode 100644 index 0000000000..b38c61e73d --- /dev/null +++ b/pyuno/demo/pyunoenv.bat @@ -0,0 +1,23 @@ +rem +rem This file is part of the LibreOffice project. +rem +rem This Source Code Form is subject to the terms of the Mozilla Public +rem License, v. 2.0. If a copy of the MPL was not distributed with this +rem file, You can obtain one at http://mozilla.org/MPL/2.0/. +rem +rem This file incorporates work covered by the following license notice: +rem +rem Licensed to the Apache Software Foundation (ASF) under one or more +rem contributor license agreements. See the NOTICE file distributed +rem with this work for additional information regarding copyright +rem ownership. The ASF licenses this file to you under the Apache +rem License, Version 2.0 (the "License"); you may not use this file +rem except in compliance with the License. You may obtain a copy of +rem the License at http://www.apache.org/licenses/LICENSE-2.0 . +rem + +set OOOHOME= + +set PYTHONPATH=.;%OOOHOME%\program;%OOOHOME%\program\pydemo;%OOOHOME%\program\python-2.2.2;%PYTHONPATH% +set PATH=%OOOHOME%\program;%PYTHONHOME%;%OOOHOME%\program\python-2.2.2\bin;%PATH% + diff --git a/pyuno/demo/pyunoenv.tcsh b/pyuno/demo/pyunoenv.tcsh new file mode 100644 index 0000000000..653d72a380 --- /dev/null +++ b/pyuno/demo/pyunoenv.tcsh @@ -0,0 +1,49 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# +# the path to the office installation (e.g. /home/joe/OpenOffice.org1.1Beta) +setenv OOOHOME /src4/OpenOffice.org1.1Beta2 + +# don't modify anything beyond these lines +#--------------------------------------------- +setenv PYTHONHOME $OOOHOME/program/python + +if( ! $?LD_LIBRARY_PATH ) then + setenv LD_LIBRARY_PATH +endif + +if(! $?PYTHONPATH ) then + setenv PYTHONPATH +endif + +if( ! $?LD_LIBRARY_PATH ) then +setenv LD_LIBRARY_PATH +endif + +if( "$PYTHONPATH" != "" ) then + setenv PYTHONPATH $OOOHOME/program:$OOOHOME/program/pydemo:$OOOHOME/program/python/lib:$PYTHONPATH +else + setenv PYTHONPATH $OOOHOME/program:$OOOHOME/program/pydemo:$OOOHOME/program/python/lib +endif + +setenv LD_LIBRARY_PATH $OOOHOME/program:$LD_LIBRARY_PATH + +if( $?PYTHONHOME ) then +setenv PATH $PYTHONHOME/bin:$PATH +endif + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/swriter.py b/pyuno/demo/swriter.py new file mode 100644 index 0000000000..70af4f98d0 --- /dev/null +++ b/pyuno/demo/swriter.py @@ -0,0 +1,118 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +# bootstrap uno component context +import uno + +# a UNO struct later needed to create a document +from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +from com.sun.star.text.TextContentAnchorType import AS_CHARACTER +from com.sun.star.awt import Size + +def insertTextIntoCell( table, cellName, text, color ): + tableText = table.getCellByName( cellName ) + cursor = tableText.createTextCursor() + cursor.setPropertyValue( "CharColor", color ) + tableText.setString( text ) + +localContext = uno.getComponentContext() + +resolver = localContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", localContext ) + +smgr = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" ) +remoteContext = smgr.getPropertyValue( "DefaultContext" ) + +#remoteContext = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" ) +#smgr = remoteContext.ServiceManager + +desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",remoteContext) + +# open a writer document +doc = desktop.loadComponentFromURL( "private:factory/swriter","_blank", 0, () ) + +text = doc.Text +cursor = text.createTextCursor() +text.insertString( cursor, "The first line in the newly created text document.\n", 0 ) +text.insertString( cursor, "Now we are in the second line\n" , 0 ) + +# create a text table +table = doc.createInstance( "com.sun.star.text.TextTable" ) + +# with 4 rows and 4 columns +table.initialize(4, 4) + +text.insertTextContent( cursor, table, 0 ) +rows = table.Rows + +table.setPropertyValue( "BackTransparent", False ) +table.setPropertyValue( "BackColor", 13421823 ) +row = rows[0] +row.setPropertyValue( "BackTransparent", False ) +row.setPropertyValue( "BackColor", 6710932 ) + +textColor = 16777215 + +insertTextIntoCell( table, "A1", "FirstColumn", textColor ) +insertTextIntoCell( table, "B1", "SecondColumn", textColor ) +insertTextIntoCell( table, "C1", "ThirdColumn", textColor ) +insertTextIntoCell( table, "D1", "SUM", textColor ) + +table.getCellByName("A2").setValue(22.5) +table.getCellByName("B2").setValue(5615.3) +table.getCellByName("C2").setValue(-2315.7) +table.getCellByName("D2").setFormula("sum <A2:C2>") + +table.getCellByName("A3").setValue(21.5) +table.getCellByName("B3").setValue(615.3) +table.getCellByName("C3").setValue(-315.7) +table.getCellByName("D3").setFormula("sum <A3:C3>") + +table.getCellByName("A4").setValue(121.5) +table.getCellByName("B4").setValue(-615.3) +table.getCellByName("C4").setValue(415.7) +table.getCellByName("D4").setFormula("sum <A4:C4>") + + +cursor.setPropertyValue( "CharColor", 255 ) +cursor.setPropertyValue( "CharShadowed", True ) + +text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) +text.insertString( cursor, " This is a colored Text - blue with shadow\n" , 0 ) +text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) + +textFrame = doc.createInstance( "com.sun.star.text.TextFrame" ) +textFrame.setSize( Size(15000,400)) +textFrame.setPropertyValue( "AnchorType" , AS_CHARACTER ) + + +text.insertTextContent( cursor, textFrame, 0 ) + +textInTextFrame = textFrame.getText() +cursorInTextFrame = textInTextFrame.createTextCursor() +textInTextFrame.insertString( cursorInTextFrame, "The first line in the newly created text frame.", 0 ) +textInTextFrame.insertString( cursorInTextFrame, "\nWith this second line the height of the rame raises.",0) +text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) + +cursor.setPropertyValue( "CharColor", 65536 ) +cursor.setPropertyValue( "CharShadowed", False ) + +text.insertString( cursor, " That's all for now!" , 0 ) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/swritercomp.py b/pyuno/demo/swritercomp.py new file mode 100644 index 0000000000..7c200cc7be --- /dev/null +++ b/pyuno/demo/swritercomp.py @@ -0,0 +1,128 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +# just a simple copy of the swriter.py demo, but implemented as a component. The advantage is, +# that the component may run within the office process which may give a performance improvement. + +import unohelper +import uno + +# a UNO struct later needed to create a document +from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +from com.sun.star.text.TextContentAnchorType import AS_CHARACTER +from com.sun.star.awt import Size + +from com.sun.star.lang import XMain + +def insertTextIntoCell( table, cellName, text, color ): + tableText = table.getCellByName( cellName ) + cursor = tableText.createTextCursor() + cursor.setPropertyValue( "CharColor", color ) + tableText.setString( text ) + +# the UNO component +# implementing the interface com.sun.star.lang.XMain +# unohelper.Base implements the XTypeProvider interface +class SWriterComp(XMain,unohelper.Base): + def __init__( self, ctx ): + self.ctx = ctx + + # implementation for XMain.run( [in] sequence< any > ) + def run( self,args ): + ctx = self.ctx + smgr = ctx.ServiceManager + desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx) + + # open a writer document + doc = desktop.loadComponentFromURL( "private:factory/swriter","_blank", 0, () ) + + text = doc.Text + cursor = text.createTextCursor() + text.insertString( cursor, "The first line in the newly created text document.\n", 0 ) + text.insertString( cursor, "Now we are in the second line\n" , 0 ) + + # create a text table + table = doc.createInstance( "com.sun.star.text.TextTable" ) + + # with 4 rows and 4 columns + table.initialize( 4,4) + + text.insertTextContent( cursor, table, 0 ) + rows = table.Rows + + table.setPropertyValue( "BackTransparent", uno.Bool(0) ) + table.setPropertyValue( "BackColor", 13421823 ) + row = rows[0] + row.setPropertyValue( "BackTransparent", uno.Bool(0) ) + row.setPropertyValue( "BackColor", 6710932 ) + + textColor = 16777215 + + insertTextIntoCell( table, "A1", "FirstColumn", textColor ) + insertTextIntoCell( table, "B1", "SecondColumn", textColor ) + insertTextIntoCell( table, "C1", "ThirdColumn", textColor ) + insertTextIntoCell( table, "D1", "SUM", textColor ) + + table.getCellByName("A2").setValue(22.5) + table.getCellByName("B2").setValue(5615.3) + table.getCellByName("C2").setValue(-2315.7) + table.getCellByName("D2").setFormula("sum <A2:C2>") + + table.getCellByName("A3").setValue(21.5) + table.getCellByName("B3").setValue(615.3) + table.getCellByName("C3").setValue(-315.7) + table.getCellByName("D3").setFormula("sum <A3:C3>") + + table.getCellByName("A4").setValue(121.5) + table.getCellByName("B4").setValue(-615.3) + table.getCellByName("C4").setValue(415.7) + table.getCellByName("D4").setFormula("sum <A4:C4>") + + + cursor.setPropertyValue( "CharColor", 255 ) + cursor.setPropertyValue( "CharShadowed", uno.Bool(1) ) + + text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) + text.insertString( cursor, " This is a colored Text - blue with shadow\n" , 0 ) + text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) + + textFrame = doc.createInstance( "com.sun.star.text.TextFrame" ) + textFrame.setSize( Size(15000,400)) + textFrame.setPropertyValue( "AnchorType" , AS_CHARACTER ) + + text.insertTextContent( cursor, textFrame, 0 ) + + textInTextFrame = textFrame.getText() + cursorInTextFrame = textInTextFrame.createTextCursor() + textInTextFrame.insertString( cursorInTextFrame, "The first line in the newly created text frame.", 0 ) + textInTextFrame.insertString( cursorInTextFrame, "\nWith this second line the height of the rame raises.",0) + text.insertControlCharacter( cursor, PARAGRAPH_BREAK, 0 ) + + cursor.setPropertyValue( "CharColor", 65536 ) + cursor.setPropertyValue( "CharShadowed", uno.Bool(0) ) + + text.insertString( cursor, " That's all for now!" , 0 ) + return 0 + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() +g_ImplementationHelper.addImplementation( \ + SWriterComp,"org.openoffice.comp.pyuno.swriter",("org.openoffice.demo.SWriter",),) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/demo/swritercompclient.py b/pyuno/demo/swritercompclient.py new file mode 100644 index 0000000000..c4d6cdbf6a --- /dev/null +++ b/pyuno/demo/swritercompclient.py @@ -0,0 +1,32 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +import uno + +localContext = uno.getComponentContext() +resolver = localContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", localContext ) +remoteContext = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" ) +remoteSmgr = remoteContext.ServiceManager + +pyComp = remoteSmgr.createInstanceWithContext( "org.openoffice.demo.SWriter" , remoteContext ) + +pyComp.run( (), ) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/doc/modes.sxd b/pyuno/doc/modes.sxd Binary files differnew file mode 100644 index 0000000000..848912b923 --- /dev/null +++ b/pyuno/doc/modes.sxd diff --git a/pyuno/inc/pyuno.hxx b/pyuno/inc/pyuno.hxx new file mode 100644 index 0000000000..f7cab36c73 --- /dev/null +++ b/pyuno/inc/pyuno.hxx @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#pragma once + +#ifndef Py_PYTHON_H +#include <Python.h> +#endif // #ifdef Py_PYTHON_H + +#include <com/sun/star/uno/Any.hxx> + +namespace com::sun::star::uno { class XComponentContext; } +namespace com::sun::star::uno { template <typename > class Reference; } + +/** + External interface of the Python UNO bridge. + + This is a C++ interface, because the core UNO components + invocation and proxyfactory are used to implement the bridge. + + This interface is somewhat private and my change in future. + + A scripting framework implementation may use this interface + to do the necessary conversions. +*/ + +#if defined LO_DLLIMPLEMENTATION_PYUNO +#define LO_DLLPUBLIC_PYUNO SAL_DLLPUBLIC_EXPORT +#else +#define LO_DLLPUBLIC_PYUNO SAL_DLLPUBLIC_IMPORT +#endif + +/** function called by the python runtime to initialize the + pyuno module. + + preconditions: python has been initialized before and + the global interpreter lock is held +*/ + +extern "C" LO_DLLPUBLIC_PYUNO PyObject* PyInit_pyuno(); + +namespace pyuno +{ + +enum NotNull +{ + /** definition of a no acquire enum for ctors + */ + NOT_NULL +}; + +/** Helper class for keeping references to python objects. + BEWARE: Look up every python function you use to check + whether you get an acquired or not acquired object pointer + (python terminus for a not acquired object pointer + is 'borrowed reference'). Use in the acquired pointer cases the + PyRef( pointer, SAL_NO_ACQUIRE) ctor. + + precondition: python has been initialized before and + the global interpreter lock is held + +*/ +class PyRef +{ + PyObject *m; +public: + PyRef () : m(nullptr) {} + PyRef( PyObject * p ) : m( p ) { Py_XINCREF( m ); } + + PyRef( PyObject * p, __sal_NoAcquire ) : m( p ) {} + + PyRef( PyObject * p, __sal_NoAcquire, NotNull ) : m( p ) + { + if (!m) + throw std::bad_alloc(); + } + + PyRef(const PyRef &r) : m(r.get()) { Py_XINCREF(m); } + PyRef(PyRef &&r) noexcept : m(r.get()) { r.scratch(); } + + ~PyRef() { Py_XDECREF( m ); } + + PyObject *get() const noexcept { return m; } + + PyObject * getAcquired() const + { + Py_XINCREF( m ); + return m; + } + + PyRef& operator=(const PyRef& r) + { + PyObject *tmp = m; + m = r.getAcquired(); + Py_XDECREF(tmp); + return *this; + } + + PyRef& operator=(PyRef&& r) + { + PyObject *tmp = m; + m = r.get(); + r.scratch(); + Py_XDECREF(tmp); + return *this; + } + + bool operator == ( const PyRef & r ) const + { + return r.get() == m; + } + + /** clears the reference without decreasing the reference count + only seldom needed ! */ + void scratch() noexcept + { + m = nullptr; + } + + /** returns 1 when the reference points to a python object python object, + otherwise 0. + */ + bool is() const + { + return m != nullptr; + } + + struct Hash + { + sal_IntPtr operator () ( const PyRef &r) const { return reinterpret_cast<sal_IntPtr>( r.get() ); } + }; +}; + +//struct stRuntimeImpl; +typedef struct stRuntimeImpl RuntimeImpl; + +enum ConversionMode { ACCEPT_UNO_ANY, REJECT_UNO_ANY }; + + +/** The pyuno::Runtime class keeps the internal state of the python UNO bridge + for the currently in use python interpreter. + + You may keep a Runtime instance, use it from a different thread, etc. But you must + make sure to fulfill all preconditions mentioned for the specific methods. +*/ + +class LO_DLLPUBLIC_PYUNO Runtime +{ + RuntimeImpl *impl; + + /** + Safely unpacks a Python iterator into a sequence, then + stores it in an Any. Used internally by pyObject2Any + */ + bool pyIterUnpack( PyObject *const, css::uno::Any & ) const; +public: + ~Runtime( ); + + /** + preconditions: python has been initialized before, + the global interpreter lock is held and pyuno + has been initialized for the currently used interpreter. + + Note: This method exists for efficiency reasons to save + lookup costs for any2PyObject and pyObject2Any + + @throw RuntimeException in case the runtime has not been + initialized before + */ + Runtime(); + + Runtime( const Runtime & ); + Runtime & operator = ( const Runtime & ); + + /** Initializes the python-UNO bridge. May be called only once per python interpreter. + + @param ctx the component context is used to instantiate bridge services needed + for bridging such as invocation, typeconverter, invocationadapterfactory, etc. + + preconditions: python has been initialized before and + the global interpreter lock is held and pyuno is not + initialized (see isInitialized() ). + + @throw RuntimeException in case the thread is not attached or the runtime + has not been initialized. + */ + static void initialize( + const css::uno::Reference< css::uno::XComponentContext > & ctx ); + + /** Checks, whether the uno runtime is already initialized in the current python interpreter. + + @throws css::uno::RuntimeException + */ + static bool isInitialized(); + + /** converts something contained in a UNO Any to a Python object + + preconditions: python has been initialized before, + the global interpreter lock is held and pyuno::Runtime + has been initialized. + + @throws css::script::CannotConvertException + @throws css::lang::IllegalArgumentException + @throws css::uno::RuntimeException + */ + PyRef any2PyObject (const css::uno::Any &source ) const; + + /** converts a Python object to a UNO any + + preconditions: python has been initialized before, + the global interpreter lock is held and pyuno + has been initialized + + @throws css::uno::RuntimeException + */ + css::uno::Any pyObject2Any ( + const PyRef & source , enum ConversionMode mode = REJECT_UNO_ANY ) const; + + /** extracts a proper uno exception from a given python exception + */ + css::uno::Any extractUnoException( + const PyRef & excType, const PyRef & excValue, const PyRef & excTraceback) const; + + /** Returns the internal handle. Should only be used by the module implementation + */ + RuntimeImpl *getImpl() const { return impl; } +}; + + +/** helper class for attaching the current thread to the python runtime. + + Attaching is done creating a new threadstate for the given interpreter + and acquiring the global interpreter lock. + + Usage: + + ... don't use python here + { + PyThreadAttach guard( PyInterpreterState_Head() ); + { + ... do whatever python code you want + { + PyThreadDetach antiguard; + ... don't use python here + } + ... do whatever python code you want + } + } + ... don't use python here + + Note: The additional scope brackets after the PyThreadAttach are needed, + e.g. when you would leave them away, dtors of potential pyrefs + may be called after the thread has detached again. + */ +class LO_DLLPUBLIC_PYUNO PyThreadAttach +{ + PyThreadState *tstate; + bool m_isNewState; + PyThreadAttach ( const PyThreadAttach & ) = delete; + PyThreadAttach & operator = ( const PyThreadAttach & ) = delete; +public: + + /** Creates a new python threadstate and acquires the global interpreter lock. + precondition: The current thread MUST NOT hold the global interpreter lock. + postcondition: The global interpreter lock is acquired + + @throws css::uno::RuntimeException + in case no pythread state could be created + */ + PyThreadAttach( PyInterpreterState *interp); + + + /** Releases the global interpreter lock and destroys the thread state. + */ + ~PyThreadAttach(); +}; + +/** helper class for detaching the current thread from the python runtime + to do some blocking, non-python related operation. + + @see PyThreadAttach +*/ +class PyThreadDetach +{ + PyThreadState *tstate; + PyThreadDetach ( const PyThreadDetach & ) = delete; + PyThreadDetach & operator = ( const PyThreadDetach & ) = delete; + +public: + /** Releases the global interpreter lock. + + precondition: The current thread MUST hold the global interpreter lock. + postcondition: The current thread does not hold the global interpreter lock anymore. + + @throws css::uno::RuntimeException + */ + PyThreadDetach(); + /** Acquires the global interpreter lock again + */ + ~PyThreadDetach(); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/qa/pytests/insertremovecells.py b/pyuno/qa/pytests/insertremovecells.py new file mode 100644 index 0000000000..2583fdb134 --- /dev/null +++ b/pyuno/qa/pytests/insertremovecells.py @@ -0,0 +1,77 @@ +import pathlib +import unittest + +from org.libreoffice.unotest import pyuno, mkPropertyValue, makeCopyFromTDOC + + +class InsertRemoveCells(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.xContext = pyuno.getComponentContext() + pyuno.private_initTestEnvironment() + + def test_fdo74824_load(self): + ctxt = self.xContext + assert(ctxt) + + smgr = ctxt.ServiceManager + desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop', ctxt) + load_props = tuple(mkPropertyValue(k, v) for (k, v) in ( + ('Hidden', True), + ('ReadOnly', False) + )) + tdoc_path = makeCopyFromTDOC('fdo74824.ods') + url = pathlib.Path(tdoc_path).as_uri() + doc = desktop.loadComponentFromURL(url, "_blank", 0, load_props) + + sheet = doc.Sheets.Sheet1 + area = sheet.getCellRangeByName('A2:B4') + addr = area.getRangeAddress() + + # 2 = intended to shift cells right, but I don't know where to find + # the ENUM to put in its place. Corrections welcome. + sheet.insertCells(addr, 2) + + # basically, the insertCells call is the test: it should not crash + # LibreOffice. However, for completeness, we should test the cell + # contents as well. + + empty_cells = ( + (0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), + (3, 1), (4, 0), (4, 2), (5, 0), (5, 2), (5, 3), + ) + formula_cells = ( + (2, 0, '=(1+GDR)^-D1', '1.000', 1.0), + (4, 1, '=(1+GDR)^-F2', '0.125', 0.125), + (4, 3, '=SUM(C1:C2)', '1.000', 1.0), + ) + value_cells = ( + (2, 2, '2010', 2010.0), + (2, 3, '7', 7.0), + (3, 0, '0', 0), + (3, 2, '2012', 2012.0), + (3, 3, '6', 6.0), + (5, 1, '1', 1.0), + ) + for col, row in empty_cells: + cell = sheet[row,col] + self.assertEqual('EMPTY', cell.Type.value) + + for col, row, f, s, val in formula_cells: + cell = sheet[row,col] + self.assertEqual('FORMULA', cell.Type.value) + self.assertEqual(f, cell.getFormula()) + self.assertEqual(s, cell.String) + self.assertEqual(val, cell.Value) + + for col, row, s, val in value_cells: + cell = sheet[row,col] + self.assertEqual(s, cell.String) + self.assertEqual(val, cell.Value) + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyuno/qa/pytests/testcollections_XCellRange.py b/pyuno/qa/pytests/testcollections_XCellRange.py new file mode 100644 index 0000000000..6ab827ab33 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XCellRange.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase +from com.sun.star.table import CellAddress + +# TextTable instance factory +def getTextTableInstance(doc): + return doc.createInstance('com.sun.star.text.TextTable') + +# Tests behaviour of objects implementing XCellRange using the new-style +# collection accessors + +class TestXCellRange(CollectionsTestBase): + + # TODO negative indices + + # Tests syntax: + # cell = cellrange[0,0] # Access cell by indices + # For: + # Spreadsheet + # Cell at Row 0, Col 0 + def test_XCellRange_Spreadsheet_Cell_00(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + cell = sht[0, 0] + + # Then + self.assertEqual(0, cell.CellAddress.Sheet) + self.assertEqual(0, cell.CellAddress.Row) + self.assertEqual(0, cell.CellAddress.Column) + + spr.close(True) + + # Tests syntax: + # cell = cellrange[0,0] # Access cell by indices + # For: + # Text table + # Cell at Row 0, Col 0 + def test_XCellRange_Table_Cell_00(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + + # When + cell = tbl[0, 0] + + # Then + self.assertEqual('A1', cell.CellName) + + doc.close(True) + + # Tests syntax: + # cell = cellrange[0,0] # Access cell by indices + # For: + # Spreadsheet + # Cell at Row 3, Col 7 + def test_XCellRange_Spreadsheet_Cell_37(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[3, 7] + + # Then + self.assertEqual(0, rng.CellAddress.Sheet) + self.assertEqual(3, rng.CellAddress.Row) + self.assertEqual(7, rng.CellAddress.Column) + + spr.close(True) + + # Tests syntax: + # cell = cellrange[0,0] # Access cell by indices + # For: + # Text table + # Cell at Row 3, Col 7 + def test_XCellRange_Table_Cell_37(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + + # When + cell = tbl[3, 7] + + # Then + self.assertEqual('H4', cell.CellName) + + doc.close(True) + + # Tests syntax: + # rng = cellrange[0,1:2] # Access cell range by index,slice + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_Index_Slice(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[0, 1:3] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(0, rng.RangeAddress.StartRow) + self.assertEqual(1, rng.RangeAddress.StartColumn) + self.assertEqual(0, rng.RangeAddress.EndRow) + self.assertEqual(2, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[0,1:2] # Access cell range by index,slice + # For: + # Text table + def test_XCellRange_Table_Range_Index_Slice(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + doc.lockControllers() + tbl.DataArray = tuple(tuple(str(100 + y) for y in range(10*x, 10*x + 10)) for x in range(10)) + doc.unlockControllers() + + # When + rng = tbl[0, 1:3] + + # Then + self.assertEqual((('101', '102'),), rng.DataArray) + + doc.close(True) + + # Tests syntax: + # rng = cellrange[1:2,0] # Access cell range by slice,index + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_Slice_Index(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[1:3, 0] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(1, rng.RangeAddress.StartRow) + self.assertEqual(0, rng.RangeAddress.StartColumn) + self.assertEqual(2, rng.RangeAddress.EndRow) + self.assertEqual(0, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[1:2,0] # Access cell range by index,slice + # For: + # Text table + def test_XCellRange_Table_Range_Slice_Index(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + doc.lockControllers() + tbl.DataArray = tuple(tuple(str(100 + y) for y in range(10*x, 10*x + 10)) for x in range(10)) + doc.unlockControllers() + + # When + rng = tbl[1:3, 0] + + # Then + self.assertEqual((('110',), ('120',)), rng.DataArray) + + doc.close(True) + + # Tests syntax: + # rng = cellrange[0:1,2:3] # Access cell range by slices + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_Slices(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[1:3, 3:5] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(1, rng.RangeAddress.StartRow) + self.assertEqual(3, rng.RangeAddress.StartColumn) + self.assertEqual(2, rng.RangeAddress.EndRow) + self.assertEqual(4, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[0:1,2:3] # Access cell range by slices + # For: + # Spreadsheet + # Zero rows/columns + def test_XCellRange_Spreadsheet_Range_Slices_Invalid(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When / Then + with self.assertRaises(KeyError): + rng = sht[1:1, 3:5] + with self.assertRaises(KeyError): + rng = sht[1:3, 3:3] + + spr.close(True) + + # Tests syntax: + # rng = cellrange[0:1,2:3] # Access cell range by slices + # For: + # Text table + def test_XCellRange_Table_Range_Slices(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + doc.lockControllers() + tbl.DataArray = tuple(tuple(str(100 + y) for y in range(10*x, 10*x + 10)) for x in range(10)) + doc.unlockControllers() + + # When + rng = tbl[1:3, 3:5] + + # Then + self.assertEqual((('113', '114'), ('123', '124')), rng.DataArray) + + doc.close(True) + + # Tests syntax: + # rng = cellrange['A1:B2'] # Access cell range by descriptor + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_Descriptor(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht['A3:B4'] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(2, rng.RangeAddress.StartRow) + self.assertEqual(0, rng.RangeAddress.StartColumn) + self.assertEqual(3, rng.RangeAddress.EndRow) + self.assertEqual(1, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange['A1:B2'] # Access cell range by descriptor + # For: + # Table + def test_XCellRange_Table_Range_Descriptor(self): + # Given + doc = self.createBlankTextDocument() + text_table = getTextTableInstance(doc) + text_table.initialize(10, 10) + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + tbl = doc.TextTables[0] + doc.lockControllers() + tbl.DataArray = tuple(tuple(str(100 + y) for y in range(10*x, 10*x + 10)) for x in range(10)) + doc.unlockControllers() + + # When + rng = tbl['A3:B4'] + + # Then + self.assertEqual((('120', '121'), ('130', '131')), rng.DataArray) + + doc.close(True) + + # Tests syntax: + # rng = cellrange['Name'] # Access cell range by name + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_Name(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + expr = '$' + sht.Name + '.$C2:F10' + addr = CellAddress(Sheet=0, Row=1, Column=2) + sht.NamedRanges.addNewByName('foo', expr, addr, 0) + + # When + rng = sht['foo'] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(1, rng.RangeAddress.StartRow) + self.assertEqual(2, rng.RangeAddress.StartColumn) + self.assertEqual(9, rng.RangeAddress.EndRow) + self.assertEqual(5, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[0] # Access cell range by row index + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_RowIndex(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[0] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(0, rng.RangeAddress.StartRow) + self.assertEqual(0, rng.RangeAddress.StartColumn) + self.assertEqual(0, rng.RangeAddress.EndRow) + self.assertEqual(16383, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[0,:] # Access cell range by row index + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_RowIndex_FullSlice(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[0, :] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(0, rng.RangeAddress.StartRow) + self.assertEqual(0, rng.RangeAddress.StartColumn) + self.assertEqual(0, rng.RangeAddress.EndRow) + self.assertEqual(16383, rng.RangeAddress.EndColumn) + + spr.close(True) + + # Tests syntax: + # rng = cellrange[:,0] # Access cell range by column index + # For: + # Spreadsheet + def test_XCellRange_Spreadsheet_Range_FullSlice_ColumnIndex(self): + # Given + spr = self.createBlankSpreadsheet() + sht = spr.Sheets[0] + + # When + rng = sht[:, 0] + + # Then + self.assertEqual(0, rng.RangeAddress.Sheet) + self.assertEqual(0, rng.RangeAddress.StartRow) + self.assertEqual(0, rng.RangeAddress.StartColumn) + self.assertEqual(1048575, rng.RangeAddress.EndRow) + self.assertEqual(0, rng.RangeAddress.EndColumn) + + spr.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XEnumeration.py b/pyuno/qa/pytests/testcollections_XEnumeration.py new file mode 100644 index 0000000000..a12d303596 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XEnumeration.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# Tests behaviour of objects implementing XEnumeration using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXEnumeration(CollectionsTestBase): + + # Tests syntax: + # for val in itr: ... # Iteration of named iterator + # For: + # 1 element + def test_XEnumeration_ForIn(self): + # Given + doc = self.createBlankTextDocument() + + # When + paragraphs = [] + itr = iter(doc.Text.createEnumeration()) + for para in itr: + paragraphs.append(para) + + # Then + self.assertEqual(1, len(paragraphs)) + + doc.close(True) + + # Tests syntax: + # if val in itr: ... # Test value presence + # For: + # Present value + def test_XEnumeration_IfIn_Present(self): + # Given + doc = self.createBlankTextDocument() + + # When + paragraph = doc.Text.createEnumeration().nextElement() + itr = iter(doc.Text.createEnumeration()) + result = paragraph in itr + + # Then + self.assertTrue(result) + + doc.close(True) + + # Tests syntax: + # if val in itr: ... # Test value presence + # For: + # Absent value + def test_XEnumeration_IfIn_Absent(self): + # Given + doc1 = self.createBlankTextDocument() + doc2 = self.createBlankTextDocument() + + # When + paragraph = doc2.Text.createEnumeration().nextElement() + itr = iter(doc1.Text.createEnumeration()) + result = paragraph in itr + + # Then + self.assertFalse(result) + + doc1.close(True) + doc2.close(True) + + # Tests syntax: + # if val in itr: ... # Test value presence + # For: + # None + def test_XEnumeration_IfIn_None(self): + # Given + doc = self.createBlankTextDocument() + + # When + itr = iter(doc.Text.createEnumeration()) + result = None in itr + + # Then + self.assertFalse(result) + + doc.close(True) + + # Tests syntax: + # if val in itr: ... # Test value presence + # For: + # Invalid value (string) + # Note: Ideally this would raise TypeError in the same manner as for + # XEnumerationAccess, but an XEnumeration doesn't know the type of its + # values + def test_XEnumeration_IfIn_String(self): + # Given + doc = self.createBlankTextDocument() + + # When + itr = iter(doc.Text.createEnumeration()) + result = 'foo' in itr + + # Then + self.assertFalse(result) + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XEnumerationAccess.py b/pyuno/qa/pytests/testcollections_XEnumerationAccess.py new file mode 100644 index 0000000000..385514ae77 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XEnumerationAccess.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# Tests behaviour of objects implementing XEnumerationAccess using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXEnumerationAccess(CollectionsTestBase): + + # Tests syntax: + # for val in obj: ... # Implicit iterator + # For: + # 1 element + def test_XEnumerationAccess_ForIn(self): + # Given + doc = self.createBlankTextDocument() + + # When + paragraphs = [] + for para in doc.Text: + paragraphs.append(para) + + # Then + self.assertEqual(1, len(paragraphs)) + + doc.close(True) + + # Tests syntax: + # itr = iter(obj) # Named iterator + # For: + # 1 element + def test_XEnumerationAccess_Iter(self): + # Given + doc = self.createBlankTextDocument() + + # When + itr = iter(doc.Text) + + # Then + self.assertIsNotNone(next(itr)) + with self.assertRaises(StopIteration): + next(itr) + + doc.close(True) + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Present value + def test_XEnumerationAccess_IfIn_Present(self): + # Given + doc = self.createBlankTextDocument() + + # When + paragraph = doc.Text.createEnumeration().nextElement() + result = paragraph in doc.Text + + # Then + self.assertTrue(result) + + doc.close(True) + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Absent value + def test_XEnumerationAccess_IfIn_Absent(self): + # Given + doc1 = self.createBlankTextDocument() + doc2 = self.createBlankTextDocument() + + # When + paragraph = doc2.Text.createEnumeration().nextElement() + result = paragraph in doc1.Text + + # Then + self.assertFalse(result) + + doc1.close(True) + doc2.close(True) + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # None + def test_XEnumerationAccess_IfIn_None(self): + # Given + doc = self.createBlankTextDocument() + + # When + result = None in doc.Text + + # Then + self.assertFalse(result) + + doc.close(True) + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Invalid value (string) + def test_XEnumerationAccess_IfIn_String(self): + # Given + doc = self.createBlankTextDocument() + + # When + result = 'foo' in doc.Text + + # Then + self.assertFalse(result) + + doc.close(True) + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Invalid value (dict) + def test_XEnumerationAccess_IfIn_String(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + result = {} in doc.Text + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XIndexAccess.py b/pyuno/qa/pytests/testcollections_XIndexAccess.py new file mode 100644 index 0000000000..ee668b7bfb --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XIndexAccess.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from inspect import isclass +from testcollections_base import CollectionsTestBase + + +# Footnote instance factory +def getFootnoteInstance(doc): + return doc.createInstance("com.sun.star.text.Footnote") + +# Tests behaviour of objects implementing XIndexAccess using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXIndexAccess(CollectionsTestBase): + + def insertTestFootnotes(self, doc, count): + cursor = doc.Text.createTextCursor() + for i in range(count): + footnote = getFootnoteInstance(doc) + footnote.Label = 'n'+str(i) + doc.Text.insertTextContent(cursor, footnote, 0) + + def readValuesTestFixture(self, doc, count, key, expected): + # Given + doc.Text.setString('') + self.insertTestFootnotes(doc, count) + + # When + captured = None + try: + actual = doc.Footnotes[key] + except Exception as e: + captured = e + + # Then + if isclass(expected) and issubclass(expected, Exception): + # expected is exception + self.assertNotEqual(None, captured) + self.assertEqual(expected.__name__, type(captured).__name__) + elif type(expected) is tuple: + # expected is tuple + self.assertEqual(None, captured) + self.assertTrue(type(actual) is tuple) + self.assertEqual(len(expected), len(actual)) + for i in range(len(expected)): + self.assertEqual('n'+str(expected[i]), actual[i].Label) + else: + # expected is value + self.assertEqual(None, captured) + self.assertTrue(type(actual) is not tuple) + self.assertEqual('n'+str(expected), actual.Label) + + # Tests syntax: + # num = len(obj) # Number of elements + # For: + # length = 0 + def test_XIndexAccess_Len_0(self): + # Given + doc = self.createBlankTextDocument() + + # When + count = len(doc.Footnotes) + + # Then + self.assertEqual(0, count) + + doc.close(True); + + # Tests syntax: + # num = len(obj) # Number of elements + # For: + # length = 1 + def test_XIndexAccess_Len_1(self): + # Given + doc = self.createBlankTextDocument() + cursor = doc.Text.createTextCursor() + footnote = getFootnoteInstance(doc) + doc.Text.insertTextContent(cursor, footnote, 0) + + # When + count = len(doc.Footnotes) + + # Then + self.assertEqual(1, count) + + doc.close(True); + + # Tests syntax: + # val = obj[0] # Access by index + # For: + # Single indices + def test_XIndexAccess_ReadIndex_Single(self): + doc = self.createBlankTextDocument() + self.readValuesTestFixture(doc, 0, -1, IndexError) + self.readValuesTestFixture(doc, 0, 0, IndexError) + self.readValuesTestFixture(doc, 0, 1, IndexError) + self.readValuesTestFixture(doc, 1, -3, IndexError) + self.readValuesTestFixture(doc, 1, -2, IndexError) + self.readValuesTestFixture(doc, 1, -1, 0) + self.readValuesTestFixture(doc, 1, 0, 0) + self.readValuesTestFixture(doc, 1, 1, IndexError) + self.readValuesTestFixture(doc, 1, 2, IndexError) + self.readValuesTestFixture(doc, 2, -4, IndexError) + self.readValuesTestFixture(doc, 2, -3, IndexError) + self.readValuesTestFixture(doc, 2, -2, 0) + self.readValuesTestFixture(doc, 2, -1, 1) + self.readValuesTestFixture(doc, 2, 0, 0) + self.readValuesTestFixture(doc, 2, 1, 1) + self.readValuesTestFixture(doc, 2, 2, IndexError) + self.readValuesTestFixture(doc, 2, 3, IndexError) + doc.close(True); + + def test_XIndexAccess_ReadIndex_Single_Invalid(self): + doc = self.createBlankTextDocument() + self.readValuesTestFixture(doc, 0, None, TypeError) + self.readValuesTestFixture(doc, 0, 'foo', TypeError) + self.readValuesTestFixture(doc, 0, 12.34, TypeError) + self.readValuesTestFixture(doc, 0, (0, 1), TypeError) + self.readValuesTestFixture(doc, 0, [0, 1], TypeError) + self.readValuesTestFixture(doc, 0, {'a': 'b'}, TypeError) + doc.close(True); + + # Tests syntax: + # val1,val2 = obj[2:4] # Access by slice + def test_XIndexAccess_ReadSlice(self): + doc = self.createBlankTextDocument() + test_max = 4 + for i in range(test_max): + t = tuple(range(i)) + for j in [x for x in range(-test_max-2, test_max+3)] + [None]: + for k in [x for x in range(-test_max-2, test_max+3)] + [None]: + key = slice(j, k) + expected = t[key] + self.readValuesTestFixture(doc, i, key, expected) + doc.close(True); + + # Tests syntax: + # val1,val2 = obj[0:3:2] # Access by extended slice + def test_XIndexAccess_ReadExtendedSlice(self): + doc = self.createBlankTextDocument() + test_max = 4 + for i in range(test_max): + t = tuple(range(i)) + for j in [x for x in range(-test_max-2, test_max+3)] + [None]: + for k in [x for x in range(-test_max-2, test_max+3)] + [None]: + for l in [-2, -1, 2]: + key = slice(j, k, l) + expected = t[key] + self.readValuesTestFixture(doc, i, key, expected) + doc.close(True); + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Present element + def test_XIndexAccess_In_Present(self): + # Given + doc = self.createBlankTextDocument() + cursor = doc.Text.createTextCursor() + footnote = getFootnoteInstance(doc) + footnote.setLabel('foo') + doc.Text.insertTextContent(cursor, footnote, 0) + footnote = doc.Footnotes[0] + + # When + present = footnote in doc.Footnotes + + # Then + self.assertTrue(present) + + doc.close(True); + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # None + def test_XIndexAccess_In_None(self): + # Given + doc = self.createBlankTextDocument() + + # When + present = None in doc.Footnotes + + # Then + self.assertFalse(present) + + doc.close(True); + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Absent element (string) + def test_XIndexAccess_In_String(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + present = "foo" in doc.Footnotes + + # Then + self.assertFalse(present) + + doc.close(True); + + # Tests syntax: + # if val in obj: ... # Test value presence + # For: + # Absent element (dict) + def test_XIndexAccess_In_Dict(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + present = {} in doc.Footnotes + + doc.close(True); + + # Tests syntax: + # for val in obj: ... # Implicit iterator (values) + # For: + # 0 elements + def test_XIndexAccess_ForIn_0(self): + # Given + doc = self.createBlankTextDocument() + + # When + read_footnotes = [] + for f in doc.Footnotes: + read_footnotes.append(f) + + # Then + self.assertEqual(0, len(read_footnotes)) + + doc.close(True); + + # Tests syntax: + # for val in obj: ... # Implicit iterator (values) + # For: + # 1 element + def test_XIndexAccess_ForIn_1(self): + # Given + doc = self.createBlankTextDocument() + cursor = doc.Text.createTextCursor() + footnote = getFootnoteInstance(doc) + footnote.setLabel('foo') + doc.Text.insertTextContent(cursor, footnote, 0) + + # When + read_footnotes = [] + for f in doc.Footnotes: + read_footnotes.append(f) + + # Then + self.assertEqual(1, len(read_footnotes)) + self.assertEqual('foo', read_footnotes[0].Label) + + doc.close(True); + + # Tests syntax: + # for val in obj: ... # Implicit iterator (values) + # For: + # 2 elements + def test_XIndexAccess_ForIn_2(self): + # Given + doc = self.createBlankTextDocument() + cursor = doc.Text.createTextCursor() + footnote1 = getFootnoteInstance(doc) + footnote2 = getFootnoteInstance(doc) + footnote1.setLabel('foo') + footnote2.setLabel('bar') + doc.Text.insertTextContent(cursor, footnote1, 0) + doc.Text.insertTextContent(cursor, footnote2, 0) + + # When + read_footnotes = [] + for f in doc.Footnotes: + read_footnotes.append(f) + + # Then + self.assertEqual(2, len(read_footnotes)) + self.assertEqual('foo', read_footnotes[0].Label) + self.assertEqual('bar', read_footnotes[1].Label) + + doc.close(True); + + # Tests syntax: + # itr = iter(obj) # Named iterator (values) + # For: + # 1 element + def test_XIndexAccess_Iter_0(self): + # Given + doc = self.createBlankTextDocument() + cursor = doc.Text.createTextCursor() + footnote = getFootnoteInstance(doc) + footnote.setLabel('foo') + doc.Text.insertTextContent(cursor, footnote, 0) + + # When + itr = iter(doc.Footnotes) + + # Then + self.assertIsNotNone(next(itr)) + with self.assertRaises(StopIteration): + next(itr) + + doc.close(True); + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XIndexContainer.py b/pyuno/qa/pytests/testcollections_XIndexContainer.py new file mode 100644 index 0000000000..1df5f07740 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XIndexContainer.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest +import uno + +from testcollections_base import CollectionsTestBase +from com.sun.star.beans import PropertyValue + +# DataForm factory +def getDataFormInstance(doc): + return doc.createInstance("com.sun.star.form.component.DataForm") + + +# Tests behaviour of objects implementing XIndexContainer using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXIndexContainer(CollectionsTestBase): + + def generateTestPropertyValues(self, count): + sm = self.context.ServiceManager + values = sm.createInstanceWithContext("com.sun.star.document.IndexedPropertyValues", self.context) + for i in range(count): + properties = (PropertyValue(Name='n'+str(i), Value='v'+str(i)),) + uno.invoke(values, "insertByIndex", (i, uno.Any("[]com.sun.star.beans.PropertyValue", properties))) + return values + + def generateTestTuple(self, values): + properties = [] + for i in values: + properties.append((PropertyValue(Name='n'+str(i), Value='v'+str(i)),)) + return tuple(properties) + + def assignValuesTestFixture(self, count, key, values, expected): + # Given + property_values = self.generateTestPropertyValues(count) + if type(values) is list: + to_assign = self.generateTestTuple(values) + else: + to_assign = values + if not (isinstance(expected, Exception)): + to_compare = self.generateTestTuple(expected) + + # When + captured = None + try: + property_values[key] = to_assign + except Exception as e: + captured = e + + # Then + if isinstance(expected, Exception): + # expected is exception + self.assertNotEqual(None, captured) + self.assertEqual(type(expected).__name__, type(captured).__name__) + else: + # expected is list + self.assertEqual(None, captured) + self.assertEqual(len(expected), property_values.getCount()) + for i in range(property_values.getCount()): + self.assertEqual(to_compare[i][0].Name, property_values[i][0].Name) + + def deleteValuesTestFixture(self, count, key, expected): + # Given + property_values = self.generateTestPropertyValues(count) + if not (isinstance(expected, Exception)): + to_compare = self.generateTestTuple(expected) + + # When + captured = None + try: + del property_values[key] + except Exception as e: + captured = e + + # Then + if isinstance(expected, Exception): + # expected is exception + self.assertNotEqual(None, captured) + self.assertEqual(type(expected).__name__, type(captured).__name__) + else: + # expected is list + self.assertEqual(None, captured) + self.assertEqual(len(expected), property_values.getCount()) + for i in range(property_values.getCount()): + self.assertEqual(to_compare[i][0].Name, property_values[i][0].Name) + + # Tests syntax: + # obj[2:4] = val1,val2 # Replace by slice + # obj[2:3] = val1,val2 # Insert/replace by slice + # obj[2:2] = (val,) # Insert by slice + # obj[2:4] = (val,) # Replace/delete by slice + # obj[2:3] = () # Delete by slice (implicit) + # For: + # Cases requiring sequence type coercion + def test_XIndexContainer_AssignSlice(self): + base_max = 5 + assign_max = 5 + for i in range(base_max): + for j in [x for x in range(-base_max-2, base_max+3)] + [None]: + for k in [x for x in range(-base_max-2, base_max+3)] + [None]: + key = slice(j, k) + for l in range(assign_max): + assign = [y+100 for y in range(l)] + expected = list(range(i)) + expected[key] = assign + self.assignValuesTestFixture(i, key, assign, expected) + + # Tests syntax: + # obj[2:4] = val1,val2 # Replace by slice + # obj[2:3] = val1,val2 # Insert/replace by slice + # obj[2:2] = (val,) # Insert by slice + # For: + # Cases not requiring sequence type coercion + # Invalid values + def test_XIndexContainer_AssignSlice_Invalid(self): + self.assignValuesTestFixture(2, slice(0, 2), None, TypeError()) + self.assignValuesTestFixture(2, slice(0, 2), 'foo', TypeError()) + self.assignValuesTestFixture(2, slice(0, 2), 12.34, TypeError()) + self.assignValuesTestFixture(2, slice(0, 2), {'a': 'b'}, TypeError()) + self.assignValuesTestFixture(2, slice(0, 2), ('foo',), TypeError()) + self.assignValuesTestFixture(2, slice(0, 2), ('foo', 'foo'), TypeError()) + + # Tests syntax: + # obj[2:2] = (val,) # Insert by slice + # For: + # Cases not requiring sequence type coercion + def test_XIndexContainer_AssignSlice_NoCoercion(self): + # Given + doc = self.createBlankTextDocument() + form = getDataFormInstance(doc) + form.Name = 'foo' + + # When + doc.DrawPage.Forms[0:0] = (form,) + + # Then + self.assertEqual('foo', doc.DrawPage.Forms[0].Name) + + doc.close(True) + + # Tests syntax: + # obj[0:3:2] = val1,val2 # Replace by extended slice + # For: + # Cases requiring sequence type coercion + def test_XIndexContainer_AssignExtendedSlice(self): + base_max = 5 + assign_max = 5 + for i in range(base_max): + for j in [x for x in range(-base_max-2, base_max+3)] + [None]: + for k in [x for x in range(-base_max-2, base_max+3)] + [None]: + for l in [-2, -1, 1, 2]: + key = slice(j, k, l) + for m in range(assign_max): + assign = [y+100 for y in range(m)] + expected = list(range(i)) + try: + expected[key] = assign + except Exception as e: + expected = e + + self.assignValuesTestFixture(i, key, assign, expected) + + # Tests syntax: + # del obj[0] # Delete by index + def test_XIndexContainer_DelIndex(self): + base_max = 5 + for i in range(base_max): + for j in [x for x in range(-base_max-2, base_max+3)]: + expected = list(range(i)) + try: + del expected[j] + except Exception as e: + expected = e + self.deleteValuesTestFixture(i, j, expected) + + # Tests syntax: + # del obj[2:4] # Delete by slice + def test_XIndexContainer_DelSlice(self): + baseMax = 5 + for i in range(baseMax): + for j in [x for x in range(-baseMax-2, baseMax+3)] + [None]: + for k in [x for x in range(-baseMax-2, baseMax+3)] + [None]: + key = slice(j, k) + expected = list(range(i)) + try: + del expected[key] + except Exception as e: + expected = e + self.deleteValuesTestFixture(i, key, expected) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XIndexReplace.py b/pyuno/qa/pytests/testcollections_XIndexReplace.py new file mode 100644 index 0000000000..b2b2d22457 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XIndexReplace.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest +import uno + +from testcollections_base import CollectionsTestBase + + +# ContentIndex instance factory +def getContentIndexInstance(doc): + return doc.createInstance("com.sun.star.text.ContentIndex") + +# Tests behaviour of objects implementing XIndexReplace using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXIndexReplace(CollectionsTestBase): + + def generateTestContentIndex(self, doc): + index = getContentIndexInstance(doc) + for i in range(10): + styles = ('n'+str(i),) + uno.invoke(index.LevelParagraphStyles, "replaceByIndex", (i, uno.Any("[]string", styles))) + return index + + def generateTestTuple(self, values): + properties = [] + for i in values: + properties.append(('n'+str(i),),) + return tuple(properties) + + def assignValuesTestFixture(self, doc, key, values, expected): + # Given + index = self.generateTestContentIndex(doc) + to_assign = self.generateTestTuple(values) + if not (isinstance(expected, Exception)): + toCompare = self.generateTestTuple(expected) + + # When + captured = None + try: + index.LevelParagraphStyles[key] = to_assign + except Exception as e: + captured = e + + # Then + if isinstance(expected, Exception): + # expected is exception + self.assertNotEqual(None, captured) + self.assertEqual(type(expected).__name__, type(captured).__name__) + else: + # expected is list + self.assertEqual(None, captured) + for i in range(10): + self.assertEqual(toCompare[i][0], + index.LevelParagraphStyles[i][0]) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Cases requiring sequence type coercion + def test_XIndexReplace_ReplaceIndex(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When + index.LevelParagraphStyles[0] = ('Caption',) + + # Then + self.assertEqual(('Caption',), index.LevelParagraphStyles[0]) + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (None) + def test_XIndexReplace_ReplaceIndex_Invalid_None(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = None + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (String) + def test_XIndexReplace_ReplaceIndex_Invalid_String(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = 'foo' + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (Float) + def test_XIndexReplace_ReplaceIndex_Invalid_Float(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = 12.34 + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (List) + def test_XIndexReplace_ReplaceIndex_Invalid_List(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = [0, 1] + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (Dict) + def test_XIndexReplace_ReplaceIndex_Invalid_Dict(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = {'a': 'b'} + + doc.close(True) + + # Tests syntax: + # obj[0] = val # Replace by index + # For: + # Invalid value (inconsistently typed tuple) + def test_XIndexReplace_ReplaceIndex_Invalid_InconsistentTuple(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0] = ('Caption', ()) + + doc.close(True) + + # Tests syntax: + # obj[2:4] = val1,val2 # Replace by slice + # For: + # Cases requiring sequence type coercion + def test_XIndexReplace_ReplaceSlice(self): + assign_max = 12 + doc = self.createBlankTextDocument() + t = tuple(range(10)) + for j in [x for x in range(-12, 13)] + [None]: + for k in [x for x in range(-12, 13)] + [None]: + key = slice(j, k) + for l in range(assign_max): + assign = [y+100 for y in range(l)] + expected = list(range(10)) + try: + expected[key] = assign + except Exception as e: + expected = e + if (len(expected) != 10): + expected = ValueError() + self.assignValuesTestFixture(doc, key, assign, expected) + doc.close(True) + + # Tests syntax: + # obj[2:4] = val1,val2 # Replace by slice + # For: + # Invalid values (inconsistently value types in tuple) + def test_XIndexReplace_ReplaceSlice_Invalid_InconsistentTuple(self): + # Given + doc = self.createBlankTextDocument() + index = getContentIndexInstance(doc) + + # When / Then + with self.assertRaises(TypeError): + index.LevelParagraphStyles[0:2] = ( + ('Caption',), + 12.34 + ) + + doc.close(True) + + # Tests syntax: + # obj[0:3:2] = val1,val2 # Replace by extended slice + # For: + # Cases requiring sequence type coercion + def test_XIndexReplace_ReplaceExtendedSlice(self): + assign_max = 12 + doc = self.createBlankTextDocument() + t = tuple(range(10)) + for j in [x for x in range(-12, 13)] + [None]: + for k in [x for x in range(-12, 13)] + [None]: + for l in [-2, -1, 2]: + key = slice(j, k, l) + for m in range(assign_max): + assign = [y+100 for y in range(m)] + expected = list(range(10)) + try: + expected[key] = assign + except Exception as e: + expected = e + self.assignValuesTestFixture(doc, key, assign, expected) + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XNameAccess.py b/pyuno/qa/pytests/testcollections_XNameAccess.py new file mode 100644 index 0000000000..5c5ad6890b --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XNameAccess.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# Tests behaviour of objects implementing XNameAccess using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXNameAccess(CollectionsTestBase): + + # Tests syntax: + # num = len(obj) # Number of keys + # For: + # 2 elements + def test_XNameAccess_Len(self): + # Given + drw = self.createBlankDrawing() + + # When + length_categories = len(drw.Links) + length_slides = len(drw.Links['Slide'].Links) + length_master = len(drw.Links['Master Page'].Links) + + # Then + self.assertEqual(4, length_categories) + self.assertEqual(1, length_slides) + self.assertEqual(1, length_master) + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # 1/2 elements + def test_XNameAccess_ReadKey(self): + # Given + drw = self.createBlankDrawing() + drw.DrawPages[0].Name = 'foo' + + # When + link = drw.Links['Slide'].Links['foo'] + + # Then + self.assertEqual('foo', link.getName()) + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Missing key + def test_XNameAccess_ReadKey_Missing(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(KeyError): + link = drw.Links['Slide'].Links['foo'] + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Invalid key type (None) + def test_XNameAccess_ReadKey_Invalid_None(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(TypeError): + link = drw.Links[None] + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Invalid key type (float) + def test_XNameAccess_ReadKey_Invalid_Float(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(TypeError): + link = drw.Links[12.34] + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Invalid key type (tuple) + def test_XNameAccess_ReadKey_Invalid_Tuple(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(TypeError): + link = drw.Links[(1, 2)] + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Invalid key type (list) + def test_XNameAccess_ReadKey_Invalid_List(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(TypeError): + link = drw.Links[[1, 2]] + + drw.close(True) + + # Tests syntax: + # val = obj[key] # Access by key + # For: + # Invalid key type (dict) + def test_XNameAccess_ReadKey_Invalid_Dict(self): + # Given + drw = self.createBlankDrawing() + + # When / Then + with self.assertRaises(TypeError): + link = drw.Links[{'a': 'b'}] + + drw.close(True) + + # Tests syntax: + # if key in obj: ... # Test key presence + # For: + # 1/2 elements + def test_XNameAccess_In(self): + # Given + drw = self.createBlankDrawing() + drw.DrawPages[0].Name = 'foo' + + # When + present = 'foo' in drw.Links['Slide'].Links + + # Then + self.assertTrue(present) + + drw.close(True) + + # Tests syntax: + # for key in obj: ... # Implicit iterator (keys) + # For: + # 2 elements + def test_XNameAccess_ForIn(self): + # Given + drw = self.createBlankDrawing() + i = 0 + for name in drw.Links['Slide'].Links.getElementNames(): + drw.Links['Slide'].Links.getByName(name).Name = 'foo' + str(i) + i += 1 + + # When + read_links = [] + for link in drw.Links['Slide'].Links: + read_links.append(link) + + # Then + self.assertEqual(['foo0'], read_links) + + drw.close(True) + + # Tests syntax: + # itr = iter(obj) # Named iterator (keys) + # For: + # 2 elements + def test_XNameAccess_Iter(self): + # Given + drw = self.createBlankDrawing() + + # When + itr = iter(drw.Links['Slide'].Links) + + # Then + self.assertIsNotNone(next(itr)) + with self.assertRaises(StopIteration): + next(itr) + + drw.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XNameContainer.py b/pyuno/qa/pytests/testcollections_XNameContainer.py new file mode 100644 index 0000000000..a02cf4d648 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XNameContainer.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# SheetCellRanges instance factory +def getSheetCellRangesInstance(spr): + return spr.createInstance("com.sun.star.sheet.SheetCellRanges") + + +# Tests behaviour of objects implementing XNameContainer using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXNameContainer(CollectionsTestBase): + + # Tests syntax: + # obj[key] = val # Insert by key + # For: + # 0->1 element + def test_XNameContainer_InsertName(self): + # Given + spr = self.createBlankSpreadsheet() + ranges = getSheetCellRangesInstance(spr) + new_range = spr.Sheets[0][2:3,1:2] + + # When + ranges['foo'] = new_range + + # Then + self.assertEqual(1, len(ranges.ElementNames)) + + spr.close(True) + + # Tests syntax: + # obj[key] = val # Insert by key + # For: + # Invalid key + def test_XNameContainer_InsertName_Invalid(self): + # Given + spr = self.createBlankSpreadsheet() + ranges = getSheetCellRangesInstance(spr) + new_range = spr.Sheets[0][2:3,1:2] + + # When / Then + with self.assertRaises(TypeError): + ranges[12.34] = new_range + + spr.close(True) + + # Tests syntax: + # obj[key] = val # Replace by key + def test_XNameContainer_ReplaceName(self): + # Given + spr = self.createBlankSpreadsheet() + ranges = getSheetCellRangesInstance(spr) + new_range1 = spr.Sheets[0][2:3,1:2] + new_range2 = spr.Sheets[0][6:7,6:7] + + # When + ranges['foo'] = new_range1 + ranges['foo'] = new_range2 + + # Then + self.assertEqual(1, len(ranges.ElementNames)) + read_range = ranges['foo'] + self.assertEqual(6, read_range.CellAddress.Column) + + spr.close(True) + + # Tests syntax: + # del obj[key] # Delete by key + # For: + # 1/2 elements + def test_XNameContainer_DelKey(self): + # Given + spr = self.createBlankSpreadsheet() + spr.Sheets.insertNewByName('foo', 1) + + # When + del spr.Sheets['foo'] + + # Then + self.assertEqual(1, len(spr.Sheets)) + self.assertFalse('foo' in spr.Sheets) + + spr.close(True) + + # Tests syntax: + # del obj[key] # Delete by key + # For: + # Missing key + def test_XNameContainer_DelKey_Missing(self): + # Given + spr = self.createBlankSpreadsheet() + + # When / Then + with self.assertRaises(KeyError): + del spr.Sheets['foo'] + + spr.close(True) + + # Tests syntax: + # del obj[key] # Delete by key + # For: + # Invalid key (float) + def test_XNameContainer_DelKey_Invalid(self): + # Given + spr = self.createBlankSpreadsheet() + + # When / Then + with self.assertRaises(TypeError): + del spr.Sheets[12.34] + + spr.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_XNameReplace.py b/pyuno/qa/pytests/testcollections_XNameReplace.py new file mode 100644 index 0000000000..a0be04404f --- /dev/null +++ b/pyuno/qa/pytests/testcollections_XNameReplace.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase +from com.sun.star.beans import PropertyValue + + +def getScriptName(): + return 'macro://Standard.Module1.MySave()' + +# Tests behaviour of objects implementing XNameReplace using the new-style +# collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestXNameReplace(CollectionsTestBase): + + # Tests syntax: + # obj[key] = val # Replace by key + # For: + # 1 element + def test_XNameReplace_ReplaceName(self): + # Given + doc = self.createBlankTextDocument() + event_properties = (PropertyValue(Name='Script', Value=getScriptName()),) + + # When + doc.Events['OnSave'] = event_properties + + # Then + on_save = [p.Value for p in doc.Events['OnSave'] if p.Name == 'Script'][0] + self.assertEqual(getScriptName(), on_save) + + doc.close(True) + + # Tests syntax: + # obj[key] = val # Replace by key + # For: + # Invalid key + def test_XNameReplace_ReplaceName_Invalid(self): + # Given + doc = self.createBlankTextDocument() + event_properties = (PropertyValue(Name='Script', Value=getScriptName()),) + + # When / Then + with self.assertRaises(KeyError): + doc.Events['qqqqq'] = event_properties + + doc.close(True) + + # Tests syntax: + # obj[key] = val # Replace by key + # For: + # Invalid key type + def test_XNameReplace_ReplaceName_Invalid(self): + # Given + doc = self.createBlankTextDocument() + event_properties = (PropertyValue(Name='Script', Value=getScriptName()),) + + # When / Then + with self.assertRaises(TypeError): + doc.Events[12.34] = event_properties + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_base.py b/pyuno/qa/pytests/testcollections_base.py new file mode 100644 index 0000000000..4dd3f26ea0 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_base.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from org.libreoffice.unotest import pyuno +from com.sun.star.beans import PropertyValue + +testEnvironmentInitialized = False + +class CollectionsTestBase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.context = pyuno.getComponentContext() + global testEnvironmentInitialized + if not testEnvironmentInitialized: + pyuno.private_initTestEnvironment() + testEnvironmentInitialized = True + + def setUp(self): + self._components = [] + + def tearDown(self): + for component in self._components: + try: + component.close(True) + except Exception: + pass + + def createHiddenWindow(self, url): + service_manager = self.context.ServiceManager + desktop = service_manager.createInstanceWithContext('com.sun.star.frame.Desktop', self.context) + load_props = ( + PropertyValue(Name='Hidden', Value=True), + PropertyValue(Name='ReadOnly', Value=False) + ) + component = desktop.loadComponentFromURL(url, '_blank', 0, load_props) + return component + + def createBlankTextDocument(self): + component = self.createHiddenWindow('private:factory/swriter') + self._components.append(component) + return component + + def createBlankSpreadsheet(self): + component = self.createHiddenWindow('private:factory/scalc') + self._components.append(component) + return component + + def createBlankDrawing(self): + component = self.createHiddenWindow('private:factory/sdraw') + self._components.append(component) + return component + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_misc.py b/pyuno/qa/pytests/testcollections_misc.py new file mode 100644 index 0000000000..7ca40de1f7 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_misc.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# Miscellaneous tests of the behaviour of UNO objects using the new-style +# collection accessors + +class TestMisc(CollectionsTestBase): + + # Tests syntax: + # for val in obj: ... # Implicit iterator + # For: + # Invalid type + def test_misc_IterateInvalidType(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + for val in doc.UIConfigurationManager: + pass + + doc.close(True) + + # Tests syntax: + # if val in itr: ... # Test value presence + # For: + # Invalid type + def test_misc_InInvalidType(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + foo = "bar" in doc.UIConfigurationManager + + doc.close(True) + + # Tests syntax: + # num = len(obj) # Number of elements + # For: + # Invalid type + def test_misc_LenInvalidType(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + len(doc.UIConfigurationManager) + + doc.close(True) + + # Tests syntax: + # val = obj[0] # Access by index + # For: + # Invalid type + def test_misc_SubscriptInvalidType(self): + # Given + doc = self.createBlankTextDocument() + + # When / Then + with self.assertRaises(TypeError): + doc.UIConfigurationManager[0] + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testcollections_misc2.py b/pyuno/qa/pytests/testcollections_misc2.py new file mode 100644 index 0000000000..86bb544eac --- /dev/null +++ b/pyuno/qa/pytests/testcollections_misc2.py @@ -0,0 +1,61 @@ + +# execute run procedure as Python macro for testing + +import uno +import unittest + +from com.sun.star.awt.FontSlant import ITALIC +from com.sun.star.awt.FontSlant import NONE +from com.sun.star.uno.TypeClass import STRING +from com.sun.star.uno.TypeClass import LONG +from com.sun.star.awt import Point + +class Test124953(unittest.TestCase): + + def test_Enum(self): + italic = uno.Enum("com.sun.star.awt.FontSlant", "ITALIC") + none_ = uno.Enum("com.sun.star.awt.FontSlant", "NONE") + self.assertEqual(ITALIC, ITALIC) + self.assertEqual(ITALIC, italic) + self.assertFalse((ITALIC != italic)) + self.assertNotEqual(ITALIC, NONE) + self.assertEqual(NONE, none_) + + def test_Type(self): + + STRING_TYPE = uno.getTypeByName("string") + LONG_TYPE = uno.getTypeByName("long") + string_type = uno.Type("string", STRING) + long_type = uno.Type("long", LONG) + self.assertEqual(STRING_TYPE, STRING_TYPE) + self.assertEqual(STRING_TYPE, string_type) + self.assertFalse((STRING_TYPE != string_type)) + self.assertNotEqual(STRING_TYPE, LONG) + self.assertEqual(LONG_TYPE, long_type) + + def test_Char(self): + char_a = uno.Char("a") + char_a2 = uno.Char("a") + char_b = uno.Char("b") + self.assertEqual(char_a, char_a) + self.assertEqual(char_a, char_a2) + self.assertFalse((char_a != char_a2)) + self.assertNotEqual(char_a, char_b) + + def test_ByteSequence(self): + b1 = uno.ByteSequence(bytes("abcdefg", encoding="utf8")) + b2 = uno.ByteSequence(bytes("abcdefg", encoding="utf8")) + b3 = uno.ByteSequence(bytes("1234567", encoding="utf8")) + self.assertEqual(b1, b1) + self.assertEqual(b1, b2) + self.assertFalse(b1 != b2) + self.assertNotEqual(b1, b3) + + def test_Struct(self): + point1 = Point(100, 200) + point2 = Point(100, 200) + point3 = Point(0, 10) + self.assertEqual(point1, point1) + self.assertEqual(point1, point2) + self.assertFalse((point1 != point2)) + self.assertNotEqual(point1, point3) diff --git a/pyuno/qa/pytests/testcollections_mixednameindex.py b/pyuno/qa/pytests/testcollections_mixednameindex.py new file mode 100644 index 0000000000..c363c0ca70 --- /dev/null +++ b/pyuno/qa/pytests/testcollections_mixednameindex.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +from testcollections_base import CollectionsTestBase + + +# Tests behaviour of objects implementing both XIndexAccess and XNameAccess +# using the new-style collection accessors +# The objects chosen have no special meaning, they just happen to implement the +# tested interfaces + +class TestMixedNameIndex(CollectionsTestBase): + + # Tests: + # Ability to access a dual XName*/XIndex* object by both name and index + def testWriterTextTableNameAndIndex(self): + # Given + doc = self.createBlankTextDocument() + text_table = doc.createInstance("com.sun.star.text.TextTable") + text_table.initialize(2, 2) + text_table.Name = 'foo' + cursor = doc.Text.createTextCursor() + doc.Text.insertTextContent(cursor, text_table, False) + + # When + table_by_name = doc.TextTables['foo'] + table_by_index = doc.TextTables[0] + + # Then + self.assertEqual('foo', table_by_name.Name) + self.assertEqual('foo', table_by_index.Name) + self.assertEqual(table_by_name, table_by_index) + + doc.close(True) + + +if __name__ == '__main__': + unittest.main() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/qa/pytests/testdocuments/fdo74824.ods b/pyuno/qa/pytests/testdocuments/fdo74824.ods Binary files differnew file mode 100644 index 0000000000..122bae22d9 --- /dev/null +++ b/pyuno/qa/pytests/testdocuments/fdo74824.ods diff --git a/pyuno/qa/pytests/testssl.py b/pyuno/qa/pytests/testssl.py new file mode 100644 index 0000000000..6ec9875fb8 --- /dev/null +++ b/pyuno/qa/pytests/testssl.py @@ -0,0 +1,10 @@ +import unittest + + +# I want to ensure that import ssl works on all platforms +class SSLTest(unittest.TestCase): + def test_ssl_import(self): + import ssl + +if __name__ == '__main__': + unittest.main() diff --git a/pyuno/source/loader/pythonloader.component b/pyuno/source/loader/pythonloader.component new file mode 100644 index 0000000000..4fc4a255ca --- /dev/null +++ b/pyuno/source/loader/pythonloader.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.comp.pyuno.Loader" + constructor="pyuno_Loader_get_implementation" single-instance="true"> + <service name="com.sun.star.loader.Python"/> + </implementation> +</component> diff --git a/pyuno/source/loader/pythonloader.py b/pyuno/source/loader/pythonloader.py new file mode 100644 index 0000000000..02f901942c --- /dev/null +++ b/pyuno/source/loader/pythonloader.py @@ -0,0 +1,163 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# +import uno +import unohelper +import sys +import types +import os +from urllib.parse import unquote +from com.sun.star.uno import Exception,RuntimeException +from com.sun.star.loader import XImplementationLoader +from com.sun.star.lang import XServiceInfo + +MODULE_PROTOCOL = "vnd.openoffice.pymodule:" +DEBUG = 0 + +g_supportedServices = "com.sun.star.loader.Python", # referenced by the native C++ loader ! +g_implementationName = "org.openoffice.comp.pyuno.Loader" # referenced by the native C++ loader ! + +def splitUrl( url ): + nColon = url.find( ":" ) + if -1 == nColon: + raise RuntimeException( "PythonLoader: No protocol in url " + url, None ) + return url[0:nColon], url[nColon+1:len(url)] + +g_loadedComponents = {} +def checkForPythonPathBesideComponent( url ): + path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" ); + if DEBUG == 1: + print(b"checking for existence of " + encfile( path )) + if 1 == os.access( encfile( path ), os.F_OK) and not path in sys.path: + if DEBUG == 1: + print(b"adding " + encfile( path ) + b" to sys.path") + sys.path.append( path ) + + path = unohelper.fileUrlToSystemPath( url+"/pythonpath" ); + if 1 == os.access( encfile( path ), os.F_OK) and not path in sys.path: + if DEBUG == 1: + print(b"adding " + encfile( path ) + b" to sys.path") + sys.path.append( path ) + +def encfile(uni): + return uni.encode( sys.getfilesystemencoding()) + +class Loader( XImplementationLoader, XServiceInfo, unohelper.Base ): + def __init__(self, ctx ): + if DEBUG: + print("pythonloader.Loader ctor") + self.ctx = ctx + + def getModuleFromUrl( self, url ): + if DEBUG: + print("pythonloader: interpreting url " + url) + protocol, dependent = splitUrl( url ) + if "vnd.sun.star.expand" == protocol: + exp = self.ctx.getValueByName( "/singletons/com.sun.star.util.theMacroExpander" ) + url = exp.expandMacros(unquote(dependent)) + protocol,dependent = splitUrl( url ) + + if DEBUG: + print("pythonloader: after expansion " + protocol + ":" + dependent) + + try: + if "file" == protocol: + # remove \..\ sequence, which may be useful e.g. in the build env + url = unohelper.absolutize( url, url ) + + # did we load the module already ? + mod = g_loadedComponents.get( url ) + if not mod: + mod = types.ModuleType("uno_component") + + # check for pythonpath.zip beside .py files + checkForPythonPathBesideComponent( url[0:url.rfind('/')] ) + + # read the file + filename = unohelper.fileUrlToSystemPath( url ) + + with open( filename, encoding='utf_8' ) as fileHandle: + src = fileHandle.read().replace("\r","") + if not src.endswith( "\n" ): + src = src + "\n" + + # compile and execute the module + codeobject = compile( src, encfile(filename), "exec" ) + mod.__file__ = filename + exec(codeobject, mod.__dict__) + g_loadedComponents[url] = mod + return mod + elif "vnd.openoffice.pymodule" == protocol: + nSlash = dependent.rfind('/') + if -1 != nSlash: + path = unohelper.fileUrlToSystemPath( dependent[0:nSlash] ) + dependent = dependent[nSlash+1:len(dependent)] + if not path in sys.path: + sys.path.append( path ) + mod = __import__( dependent ) + path_component, dot, rest = dependent.partition('.') + while dot == '.': + path_component, dot, rest = rest.partition('.') + mod = getattr(mod, path_component) + return mod + else: + if DEBUG: + print("Unknown protocol '" + protocol + "'"); + raise RuntimeException( "PythonLoader: Unknown protocol " + + protocol + " in url " +url, self ) + except Exception as e: + if DEBUG: + print ("Python import exception " + str(type(e)) + + " message " + str(e) + " args " + str(e.args)); + raise RuntimeException( "Couldn't load " + url + " for reason " + str(e), None ) + return None + + def activate( self, implementationName, dummy, locationUrl, regKey ): + if DEBUG: + print("pythonloader.Loader.activate") + + mod = self.getModuleFromUrl( locationUrl ) + implHelper = mod.__dict__.get( "g_ImplementationHelper" , None ) + if DEBUG: + print ("Fetched ImplHelper as " + str(implHelper)) + if implHelper is None: + return mod.getComponentFactory( implementationName, self.ctx.ServiceManager, regKey ) + else: + return implHelper.getComponentFactory( implementationName,regKey,self.ctx.ServiceManager) + + def writeRegistryInfo( self, regKey, dummy, locationUrl ): + if DEBUG: + print( "pythonloader.Loader.writeRegistryInfo" ) + + mod = self.getModuleFromUrl( locationUrl ) + implHelper = mod.__dict__.get( "g_ImplementationHelper" , None ) + if implHelper is None: + return mod.writeRegistryInfo( self.ctx.ServiceManager, regKey ) + else: + return implHelper.writeRegistryInfo( regKey, self.ctx.ServiceManager ) + + def getImplementationName( self ): + return g_implementationName + + def supportsService( self, ServiceName ): + return ServiceName in self.getSupportedServiceNames() + + def getSupportedServiceNames( self ): + return g_supportedServices + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/source/loader/pyuno_loader.cxx b/pyuno/source/loader/pyuno_loader.cxx new file mode 100644 index 0000000000..05a03fe72c --- /dev/null +++ b/pyuno/source/loader/pyuno_loader.cxx @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <config_features.h> +#include <config_folders.h> + +#include <pyuno.hxx> + +#include <o3tl/any.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <osl/process.h> +#include <osl/file.hxx> +#include <osl/thread.h> + +#include <rtl/ustrbuf.hxx> +#include <rtl/bootstrap.hxx> + +#include <cppuhelper/factory.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> + +// apparently PATH_MAX is not standard and not defined by MSVC +#ifndef PATH_MAX +#ifdef _MAX_PATH +#define PATH_MAX _MAX_PATH +#else +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#error no PATH_MAX +#endif +#endif +#endif + +using pyuno::PyRef; +using pyuno::NOT_NULL; +using pyuno::Runtime; +using pyuno::PyThreadAttach; + +using com::sun::star::uno::Reference; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::XComponentContext; +using com::sun::star::uno::RuntimeException; + +namespace pyuno_loader +{ + +/// @throws RuntimeException +static void raiseRuntimeExceptionWhenNeeded() +{ + if( PyErr_Occurred() ) + { + PyRef excType, excValue, excTraceback; + PyErr_Fetch(reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback)); + Runtime runtime; + css::uno::Any a = runtime.extractUnoException( excType, excValue, excTraceback ); + OUStringBuffer buf( "python-loader:" ); + if( auto e = o3tl::tryAccess<css::uno::Exception>(a) ) + buf.append( e->Message ); + throw RuntimeException( buf.makeStringAndClear() ); + } +} + +/// @throws RuntimeException +static PyRef getLoaderModule() +{ + PyRef module( + PyImport_ImportModule( "pythonloader" ), + SAL_NO_ACQUIRE ); + raiseRuntimeExceptionWhenNeeded(); + if( !module.is() ) + { + throw RuntimeException( "pythonloader: Couldn't load pythonloader module" ); + } + return PyRef( PyModule_GetDict( module.get() )); +} + +/// @throws RuntimeException +static PyRef getObjectFromLoaderModule( const char * func ) +{ + PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) ); + if( !object.is() ) + { + throw RuntimeException( "pythonloader: couldn't find core element pythonloader." + + OUString::createFromAscii( func )); + } + return object; +} + +static void setPythonHome ( const OUString & pythonHome ) +{ + OUString systemPythonHome; + osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) ); + // static because Py_SetPythonHome just copies the "wide" pointer + static wchar_t wide[PATH_MAX + 1]; +#if defined _WIN32 + const size_t len = systemPythonHome.getLength(); + if (len < std::size(wide)) + wcsncpy(wide, o3tl::toW(systemPythonHome.getStr()), len + 1); +#else + OString o = OUStringToOString(systemPythonHome, osl_getThreadTextEncoding()); + size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1); + if(len == size_t(-1)) + { + PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path"); + return; + } +#endif + if(len >= PATH_MAX + 1) + { + PyErr_SetString(PyExc_SystemError, "python home path is too long"); + return; + } +SAL_WNODEPRECATED_DECLARATIONS_PUSH + Py_SetPythonHome(wide); // deprecated since python 3.11 +SAL_WNODEPRECATED_DECLARATIONS_POP +} + +static void prependPythonPath( std::u16string_view pythonPathBootstrap ) +{ + OUStringBuffer bufPYTHONPATH( 256 ); + bool bAppendSep = false; + sal_Int32 nIndex = 0; + while( true ) + { + size_t nNew = pythonPathBootstrap.find( ' ', nIndex ); + std::u16string_view fileUrl; + if( nNew == std::u16string_view::npos ) + { + fileUrl = pythonPathBootstrap.substr(nIndex); + } + else + { + fileUrl = pythonPathBootstrap.substr(nIndex, nNew - nIndex); + } + OUString systemPath; + osl_getSystemPathFromFileURL( OUString(fileUrl).pData, &(systemPath.pData) ); + if (!systemPath.isEmpty()) + { + if (bAppendSep) + bufPYTHONPATH.append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR)); + bufPYTHONPATH.append(systemPath); + bAppendSep = true; + } + if( nNew == std::u16string_view::npos ) + break; + nIndex = nNew + 1; + } + const char * oldEnv = getenv( "PYTHONPATH"); + if( oldEnv ) + { + if (bAppendSep) + bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) ); + bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) ); + } + + OUString envVar("PYTHONPATH"); + OUString envValue(bufPYTHONPATH.makeStringAndClear()); + osl_setEnvironment(envVar.pData, envValue.pData); +} + +namespace { + +void pythonInit() { + if ( Py_IsInitialized()) // may be inited by getComponentContext() already + return; + + OUString pythonPath; + OUString pythonHome; + OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" )); + rtl::Bootstrap::expandMacros(path); //TODO: detect failure + rtl::Bootstrap bootstrap(path); + + // look for pythonhome + bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome ); + bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath ); + + // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console + // sadly, there is no api for setting the pythonpath, we have to use the environment variable + if( !pythonHome.isEmpty() ) + setPythonHome( pythonHome ); + + if( !pythonPath.isEmpty() ) + prependPythonPath( pythonPath ); + +#ifdef _WIN32 + //extend PATH under windows to include the branddir/program so ssl libs will be found + //for use by terminal mailmerge dependency _ssl.pyd + OUString sEnvName("PATH"); + OUString sPath; + osl_getEnvironment(sEnvName.pData, &sPath.pData); + OUString sBrandLocation("$BRAND_BASE_DIR/program"); + rtl::Bootstrap::expandMacros(sBrandLocation); + osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation); + sPath = sPath + OUStringChar(SAL_PATHSEPARATOR) + sBrandLocation; + osl_setEnvironment(sEnvName.pData, sPath.pData); +#endif + + PyImport_AppendInittab( "pyuno", PyInit_pyuno ); + +#if HAVE_FEATURE_READONLY_INSTALLSET + Py_DontWriteBytecodeFlag = 1; +#endif + + // initialize python + Py_Initialize(); +#if PY_VERSION_HEX < 0x03090000 + PyEval_InitThreads(); +#endif + + PyThreadState *tstate = PyThreadState_Get(); + PyEval_ReleaseThread( tstate ); +#if PY_VERSION_HEX < 0x030B0000 + // This tstate is never used again, so delete it here. + // This prevents an assertion in PyThreadState_Swap on the + // PyThreadAttach below. + PyThreadState_Delete(tstate); +#endif +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +pyuno_Loader_get_implementation( + css::uno::XComponentContext* ctx , css::uno::Sequence<css::uno::Any> const&) +{ + // tdf#114815 init python only once, via single-instace="true" in pythonloader.component + pythonInit(); + + Reference< XInterface > ret; + + PyThreadAttach attach( PyInterpreterState_Head() ); + { + // note: this can't race against getComponentContext() because + // either (in soffice.bin) CreateInstance() must be called before + // getComponentContext() can be called, or (in python.bin) the other + // way around + if( ! Runtime::isInitialized() ) + { + Runtime::initialize( ctx ); + } + Runtime runtime; + + PyRef pyCtx = runtime.any2PyObject( + css::uno::Any( css::uno::Reference(ctx) ) ); + + PyRef clazz = getObjectFromLoaderModule( "Loader" ); + PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() ); + PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE ); + runtime.pyObject2Any( pyInstance ) >>= ret; + } + ret->acquire(); + return ret.get(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno.cxx b/pyuno/source/module/pyuno.cxx new file mode 100644 index 0000000000..bd4904d5f7 --- /dev/null +++ b/pyuno/source/module/pyuno.cxx @@ -0,0 +1,1715 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> + +#include <rtl/ustrbuf.hxx> + +#include <typelib/typedescription.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XNameReplace.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XInvocation2.hpp> +#include <com/sun/star/script/XTypeConverter.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <comphelper/servicehelper.hxx> + +#include "pyuno_impl.hxx" + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Reference; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Any; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::Type; +using com::sun::star::uno::TypeClass; +using com::sun::star::uno::TypeDescription; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Exception; +using com::sun::star::lang::XSingleServiceFactory; +using com::sun::star::lang::XServiceInfo; +using com::sun::star::lang::XTypeProvider; +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::script::XInvocation2; +using com::sun::star::container::XEnumeration; +using com::sun::star::container::XEnumerationAccess; +using com::sun::star::container::XIndexAccess; +using com::sun::star::container::XIndexContainer; +using com::sun::star::container::XIndexReplace; +using com::sun::star::container::XNameAccess; +using com::sun::star::container::XNameContainer; +using com::sun::star::container::XNameReplace; + +namespace pyuno +{ + +static PyObject *PyUNO_str( PyObject * self ); + +static void PyUNO_del (PyObject* self) +{ + PyUNO* me = reinterpret_cast< PyUNO* > (self); + { + PyThreadDetach antiguard; + delete me->members; + } + PyObject_Del (self); +} + + +OUString val2str( const void * pVal, typelib_TypeDescriptionReference * pTypeRef , sal_Int32 mode ) +{ + assert( pVal ); + if (pTypeRef->eTypeClass == typelib_TypeClass_VOID) + return "void"; + + OUStringBuffer buf( 64 ); + buf.append( "(" + OUString::unacquired(&pTypeRef->pTypeName) + ")" ); + + switch (pTypeRef->eTypeClass) + { + case typelib_TypeClass_INTERFACE: + { + buf.append( "0x" + + OUString::number( reinterpret_cast< sal_IntPtr >(*static_cast<void * const *>(pVal)), 16 )); + if( VAL2STR_MODE_DEEP == mode ) + { + buf.append( "{" ); Reference< XInterface > r = *static_cast<Reference< XInterface > const *>(pVal); + Reference< XServiceInfo > serviceInfo( r, UNO_QUERY); + Reference< XTypeProvider > typeProvider(r,UNO_QUERY); + if( serviceInfo.is() ) + { + buf.append("implementationName=" + + serviceInfo->getImplementationName() + + ", supportedServices={" ); + Sequence< OUString > seq = serviceInfo->getSupportedServiceNames(); + for( int i = 0 ; i < seq.getLength() ; i ++ ) + { + buf.append( seq[i] ); + if( i +1 != seq.getLength() ) + buf.append( "," ); + } + buf.append("}"); + } + + if( typeProvider.is() ) + { + buf.append(", supportedInterfaces={" ); + Sequence< Type > seq (typeProvider->getTypes()); + for( int i = 0 ; i < seq.getLength() ; i ++ ) + { + buf.append(seq[i].getTypeName()); + if( i +1 != seq.getLength() ) + buf.append( "," ); + } + buf.append("}"); + } + buf.append( "}" ); + } + + break; + } + case typelib_TypeClass_STRUCT: + case typelib_TypeClass_EXCEPTION: + { + buf.append( "{ " ); + typelib_TypeDescription * pTypeDescr = nullptr; + TYPELIB_DANGER_GET( &pTypeDescr, pTypeRef ); + assert( pTypeDescr ); + + typelib_CompoundTypeDescription * pCompType = reinterpret_cast<typelib_CompoundTypeDescription *>(pTypeDescr); + sal_Int32 nDescr = pCompType->nMembers; + + if (pCompType->pBaseTypeDescription) + { + buf.append( val2str( pVal, pCompType->pBaseTypeDescription->aBase.pWeakRef, mode ) ); + if (nDescr) + buf.append( ", " ); + } + + typelib_TypeDescriptionReference ** ppTypeRefs = pCompType->ppTypeRefs; + sal_Int32 * pMemberOffsets = pCompType->pMemberOffsets; + rtl_uString ** ppMemberNames = pCompType->ppMemberNames; + + for ( sal_Int32 nPos = 0; nPos < nDescr; ++nPos ) + { + buf.append( OUString::unacquired(&ppMemberNames[nPos]) + " = " ); + typelib_TypeDescription * pMemberType = nullptr; + TYPELIB_DANGER_GET( &pMemberType, ppTypeRefs[nPos] ); + buf.append( val2str( static_cast<char const *>(pVal) + pMemberOffsets[nPos], pMemberType->pWeakRef, mode ) ); + TYPELIB_DANGER_RELEASE( pMemberType ); + if (nPos < (nDescr -1)) + buf.append( ", " ); + } + + TYPELIB_DANGER_RELEASE( pTypeDescr ); + + buf.append( " }" ); + break; + } + case typelib_TypeClass_SEQUENCE: + { + typelib_TypeDescription * pTypeDescr = nullptr; + TYPELIB_DANGER_GET( &pTypeDescr, pTypeRef ); + + uno_Sequence * pSequence = *static_cast<uno_Sequence * const *>(pVal); + typelib_TypeDescription * pElementTypeDescr = nullptr; + TYPELIB_DANGER_GET( &pElementTypeDescr, reinterpret_cast<typelib_IndirectTypeDescription *>(pTypeDescr)->pType ); + + sal_Int32 nElementSize = pElementTypeDescr->nSize; + sal_Int32 nElements = pSequence->nElements; + + if (nElements) + { + buf.append( "{ " ); + char * pElements = pSequence->elements; + for ( sal_Int32 nPos = 0; nPos < nElements; ++nPos ) + { + buf.append( val2str( pElements + (nElementSize * nPos), pElementTypeDescr->pWeakRef, mode ) ); + if (nPos < (nElements -1)) + buf.append( ", " ); + } + buf.append( " }" ); + } + else + { + buf.append( "{}" ); + } + TYPELIB_DANGER_RELEASE( pElementTypeDescr ); + TYPELIB_DANGER_RELEASE( pTypeDescr ); + break; + } + case typelib_TypeClass_ANY: + buf.append( "{ " + + val2str( static_cast<uno_Any const *>(pVal)->pData, + static_cast<uno_Any const *>(pVal)->pType , + mode) + + " }" ); + break; + case typelib_TypeClass_TYPE: + buf.append( (*static_cast<typelib_TypeDescriptionReference * const *>(pVal))->pTypeName ); + break; + case typelib_TypeClass_STRING: + buf.append( "\"" + + OUString::unacquired(&*static_cast<rtl_uString * const *>(pVal)) + + "\"" ); + break; + case typelib_TypeClass_ENUM: + { + typelib_TypeDescription * pTypeDescr = nullptr; + TYPELIB_DANGER_GET( &pTypeDescr, pTypeRef ); + + sal_Int32 * pValues = reinterpret_cast<typelib_EnumTypeDescription *>(pTypeDescr)->pEnumValues; + sal_Int32 nPos = reinterpret_cast<typelib_EnumTypeDescription *>(pTypeDescr)->nEnumValues; + while (nPos--) + { + if (pValues[nPos] == *static_cast<int const *>(pVal)) + break; + } + if (nPos >= 0) + buf.append( reinterpret_cast<typelib_EnumTypeDescription *>(pTypeDescr)->ppEnumNames[nPos] ); + else + buf.append( '?' ); + + TYPELIB_DANGER_RELEASE( pTypeDescr ); + break; + } + case typelib_TypeClass_BOOLEAN: + if (*static_cast<sal_Bool const *>(pVal)) + buf.append( "true" ); + else + buf.append( "false" ); + break; + case typelib_TypeClass_CHAR: + buf.append( "\'" + + OUStringChar(*static_cast<sal_Unicode const *>(pVal) ) + + "\'" ); + break; + case typelib_TypeClass_FLOAT: + buf.append( *static_cast<float const *>(pVal) ); + break; + case typelib_TypeClass_DOUBLE: + buf.append( *static_cast<double const *>(pVal) ); + break; + case typelib_TypeClass_BYTE: + buf.append( "0x" + + OUString::number( static_cast<sal_Int32>(*static_cast<sal_Int8 const *>(pVal)), 16 )); + break; + case typelib_TypeClass_SHORT: + buf.append( "0x" + + OUString::number( static_cast<sal_Int32>(*static_cast<sal_Int16 const *>(pVal)), 16 )); + break; + case typelib_TypeClass_UNSIGNED_SHORT: + buf.append( "0x" + + OUString::number( static_cast<sal_Int32>(*static_cast<sal_uInt16 const *>(pVal)), 16 )); + break; + case typelib_TypeClass_LONG: + buf.append( "0x" + + OUString::number( *static_cast<sal_Int32 const *>(pVal), 16 )); + break; + case typelib_TypeClass_UNSIGNED_LONG: + buf.append( "0x" + + OUString::number( static_cast<sal_Int64>(*static_cast<sal_uInt32 const *>(pVal)), 16 )); + break; + case typelib_TypeClass_HYPER: + case typelib_TypeClass_UNSIGNED_HYPER: + buf.append( "0x" ); +#if defined(__GNUC__) && defined(SPARC) +// I guess this really should check if there are strict alignment +// requirements, not just "GCC on SPARC". + { + sal_Int64 aVal; + *(sal_Int32 *)&aVal = *(sal_Int32 *)pVal; + *((sal_Int32 *)&aVal +1)= *((sal_Int32 *)pVal +1); + buf.append( aVal, 16 ); + } +#else + buf.append( *static_cast<sal_Int64 const *>(pVal), 16 ); +#endif + break; + + case typelib_TypeClass_VOID: + case typelib_TypeClass_UNKNOWN: + case typelib_TypeClass_SERVICE: + case typelib_TypeClass_MODULE: + default: + buf.append( '?' ); + } + + return buf.makeStringAndClear(); +} + +static sal_Int32 lcl_PyNumber_AsSal_Int32( PyObject *pObj ) +{ + // Check object is an index + PyRef rIndex( PyNumber_Index( pObj ), SAL_NO_ACQUIRE ); + if ( !rIndex.is() ) + return -1; + + // Convert Python number to platform long, then check actual value against + // bounds of sal_Int32 + int nOverflow; + long nResult = PyLong_AsLongAndOverflow( pObj, &nOverflow ); + if ( nOverflow || nResult > SAL_MAX_INT32 || nResult < SAL_MIN_INT32) { + PyErr_SetString( PyExc_IndexError, "Python int too large to convert to UNO long" ); + return -1; + } + + return nResult; +} + +static int lcl_PySlice_GetIndicesEx( PyObject *pObject, sal_Int32 nLen, sal_Int32 *nStart, sal_Int32 *nStop, sal_Int32 *nStep, sal_Int32 *nSliceLength ) +{ + Py_ssize_t nStart_ssize, nStop_ssize, nStep_ssize, nSliceLength_ssize; + + int nResult = PySlice_GetIndicesEx(pObject, + nLen, &nStart_ssize, &nStop_ssize, &nStep_ssize, &nSliceLength_ssize ); + if (nResult == -1) + return -1; + + if ( nStart_ssize > SAL_MAX_INT32 || nStart_ssize < SAL_MIN_INT32 + || nStop_ssize > SAL_MAX_INT32 || nStop_ssize < SAL_MIN_INT32 + || nStep_ssize > SAL_MAX_INT32 || nStep_ssize < SAL_MIN_INT32 + || nSliceLength_ssize > SAL_MAX_INT32 || nSliceLength_ssize < SAL_MIN_INT32 ) + { + PyErr_SetString( PyExc_IndexError, "Python int too large to convert to UNO long" ); + return -1; + } + + *nStart = static_cast<sal_Int32>(nStart_ssize); + *nStop = static_cast<sal_Int32>(nStop_ssize); + *nStep = static_cast<sal_Int32>(nStep_ssize); + *nSliceLength = static_cast<sal_Int32>(nSliceLength_ssize); + return 0; +} + +static bool lcl_hasInterfaceByName( Any const &object, OUString const & interfaceName ) +{ + Reference< XInterface > xInterface( object, UNO_QUERY ); + TypeDescription typeDesc( interfaceName ); + Any aInterface = xInterface->queryInterface( typeDesc.get()->pWeakRef ); + + return aInterface.hasValue(); +} + +static PyObject *PyUNO_repr( PyObject * self ) +{ + return PyUNO_str( self ); +} + +static Py_hash_t PyUNO_hash( PyObject *self ) +{ + + PyUNO *me = reinterpret_cast<PyUNO *>(self); + + // Py_hash_t is not necessarily the same size as a pointer, but this is not + // important for hashing - it just has to return the same value each time + return sal::static_int_cast< Py_hash_t >( reinterpret_cast< sal_IntPtr > ( + *static_cast<void * const *>(me->members->wrappedObject.getValue()) ) ); + +} + +PyObject *PyUNO_invoke( PyObject *object, const char *name , PyObject *args ) +{ + PyRef ret; + try + { + Runtime runtime; + + PyRef paras,callable; + if( PyObject_IsInstance( object, getPyUnoClass().get() ) ) + { + PyUNO* me = reinterpret_cast<PyUNO*>(object); + OUString attrName = OUString::createFromAscii(name); + if (! me->members->xInvocation->hasMethod (attrName)) + { + throw RuntimeException( "Attribute " + attrName + " unknown" ); + } + callable = PyUNO_callable_new ( + me->members->xInvocation, + attrName, + ACCEPT_UNO_ANY); + paras = args; + } + else + { + // clean the tuple from uno.Any ! + int size = PyTuple_Size( args ); + { // for CC, keeping ref-count of tuple being 1 + paras = PyRef(PyTuple_New( size ), SAL_NO_ACQUIRE); + } + for( int i = 0 ; i < size ;i ++ ) + { + PyObject * element = PyTuple_GetItem( args , i ); + if( PyObject_IsInstance( element , getAnyClass( runtime ).get() ) ) + { + element = PyObject_GetAttrString( + element, "value" ); + } + else + { + Py_XINCREF( element ); + } + PyTuple_SetItem( paras.get(), i , element ); + } + callable = PyRef( PyObject_GetAttrString( object , name ), SAL_NO_ACQUIRE ); + if( !callable.is() ) + return nullptr; + } + ret = PyRef( PyObject_CallObject( callable.get(), paras.get() ), SAL_NO_ACQUIRE ); + } + catch (const css::lang::IllegalArgumentException &e) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch (const css::script::CannotConvertException &e) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch (const css::uno::RuntimeException &e) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch (const css::uno::Exception &e) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return ret.getAcquired(); +} + +PyObject *PyUNO_str( PyObject * self ) +{ + PyUNO *me = reinterpret_cast<PyUNO *>(self); + + OString buf; + + { + PyThreadDetach antiguard; + + OUString s = val2str( me->members->wrappedObject.getValue(), + me->members->wrappedObject.getValueType().getTypeLibType() ); + buf = "pyuno object " + OUStringToOString(s,RTL_TEXTENCODING_ASCII_US); + } + + return PyUnicode_FromString( buf.getStr() ); +} + +static PyObject* PyUNO_dir (PyObject* self) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + PyObject* member_list = nullptr; + Sequence<OUString> oo_member_list; + + try + { + oo_member_list = me->members->xInvocation->getMemberNames (); + member_list = PyList_New (oo_member_list.getLength ()); + for (int i = 0; i < oo_member_list.getLength (); i++) + { + // setitem steals a reference + PyList_SetItem (member_list, i, ustring2PyString(oo_member_list[i]).getAcquired() ); + } + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + + return member_list; +} + +static sal_Int32 lcl_detach_getLength( PyUNO const *me ) +{ + PyThreadDetach antiguard; + + // If both XIndexContainer and XNameContainer are implemented, it is + // assumed that getCount() gives the same result as the number of names + // returned by getElementNames(), or the user may be surprised. + + // For XIndexContainer + Reference< XIndexAccess > xIndexAccess( me->members->xInvocation, UNO_QUERY ); + if ( xIndexAccess.is() ) + { + return xIndexAccess->getCount(); + } + + // For XNameContainer + // Not terribly efficient - get the count of all the names + Reference< XNameAccess > xNameAccess( me->members->xInvocation, UNO_QUERY ); + if ( xNameAccess.is() ) + { + return xNameAccess->getElementNames().getLength(); + } + + return -1; +} + +static int PyUNO_bool( PyObject* self ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + try + { + int nLen = lcl_detach_getLength( me ); + if (nLen >= 0) + return nLen == 0 ? 0 : 1; + + // Anything which doesn't have members is a scalar object and therefore true + return 1; + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return -1; +} + +static Py_ssize_t PyUNO_len( PyObject* self ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + try + { + int nLen = lcl_detach_getLength( me ); + if (nLen >= 0) + return nLen; + + PyErr_SetString( PyExc_TypeError, "object has no len()" ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return -1; +} + +static void lcl_getRowsColumns( PyUNO const * me, sal_Int32& nRows, sal_Int32& nColumns ) +{ + Sequence<short> aOutParamIndex; + Sequence<Any> aOutParam; + Sequence<Any> aParams; + Any aRet = me->members->xInvocation->invoke ( "getRows", aParams, aOutParamIndex, aOutParam ); + Reference< XIndexAccess > xIndexAccessRows( aRet, UNO_QUERY ); + nRows = xIndexAccessRows->getCount(); + aRet = me->members->xInvocation->invoke ( "getColumns", aParams, aOutParamIndex, aOutParam ); + Reference< XIndexAccess > xIndexAccessCols( aRet, UNO_QUERY ); + nColumns = xIndexAccessCols->getCount(); +} + +static PyRef lcl_indexToSlice( const PyRef& rIndex ) +{ + Py_ssize_t nIndex = PyNumber_AsSsize_t( rIndex.get(), PyExc_IndexError ); + if (nIndex == -1 && PyErr_Occurred()) + return nullptr; + PyRef rStart( PyLong_FromSsize_t( nIndex ), SAL_NO_ACQUIRE ); + PyRef rStop( PyLong_FromSsize_t( nIndex+1 ), SAL_NO_ACQUIRE ); + PyRef rStep( PyLong_FromLong( 1 ), SAL_NO_ACQUIRE ); + PyRef rSlice( PySlice_New( rStart.get(), rStop.get(), rStep.get() ), SAL_NO_ACQUIRE ); + + return rSlice; +} + +static PyObject* lcl_getitem_XCellRange( PyUNO const * me, PyObject* pKey ) +{ + Runtime runtime; + + Sequence<short> aOutParamIndex; + Sequence<Any> aOutParam; + Sequence<Any> aParams; + Any aRet; + + // Single string key is sugar for getCellRangeByName() + if ( PyUnicode_Check( pKey ) ) { + + aParams = { Any(pyString2ustring( pKey )) }; + { + PyThreadDetach antiguard; + aRet = me->members->xInvocation->invoke ( + "getCellRangeByName", aParams, aOutParamIndex, aOutParam ); + } + PyRef rRet = runtime.any2PyObject ( aRet ); + return rRet.getAcquired(); + + } + + PyRef rKey0, rKey1; + if ( PyIndex_Check( pKey ) ) + { + // [0] is equivalent to [0,:] + rKey0 = pKey; + rKey1 = PySlice_New( nullptr, nullptr, nullptr ); + } + else if ( PyTuple_Check( pKey ) && (PyTuple_Size( pKey ) == 2) ) + { + rKey0 = PyTuple_GetItem( pKey, 0 ); + rKey1 = PyTuple_GetItem( pKey, 1 ); + } + else + { + PyErr_SetString( PyExc_KeyError, "invalid subscript" ); + return nullptr; + } + + // If both keys are indices, return the corresponding cell + if ( PyIndex_Check( rKey0.get() ) && PyIndex_Check( rKey1.get() )) + { + sal_Int32 nKey0_s = lcl_PyNumber_AsSal_Int32( rKey0.get() ); + sal_Int32 nKey1_s = lcl_PyNumber_AsSal_Int32( rKey1.get() ); + + if ( ((nKey0_s == -1) || (nKey1_s == -1)) && PyErr_Occurred() ) + return nullptr; + + aParams = { Any(nKey1_s), Any(nKey0_s) }; + { + PyThreadDetach antiguard; + aRet = me->members->xInvocation->invoke ( + "getCellByPosition", aParams, aOutParamIndex, aOutParam ); + } + PyRef rRet = runtime.any2PyObject( aRet ); + return rRet.getAcquired(); + } + + // If either argument is an index, coerce it to a slice + if ( PyIndex_Check( rKey0.get() ) ) + rKey0 = lcl_indexToSlice( rKey0 ); + + if ( PyIndex_Check( rKey1.get() ) ) + rKey1 = lcl_indexToSlice( rKey1 ); + + // If both arguments are slices, return the corresponding cell range + if ( PySlice_Check( rKey0.get() ) && PySlice_Check( rKey1.get() ) ) + { + sal_Int32 nLen0 = SAL_MAX_INT32, nLen1 = SAL_MAX_INT32; + sal_Int32 nStart0 = 0, nStop0 = 0, nStep0 = 0, nSliceLength0 = 0; + sal_Int32 nStart1 = 0, nStop1 = 0, nStep1 = 0, nSliceLength1 = 0; + + { + PyThreadDetach antiguard; + + if ( lcl_hasInterfaceByName( me->members->wrappedObject, "com.sun.star.table.XColumnRowRange" ) ) + { + lcl_getRowsColumns (me, nLen0, nLen1); + } + } + + int nSuccess1 = lcl_PySlice_GetIndicesEx( rKey0.get(), nLen0, &nStart0, &nStop0, &nStep0, &nSliceLength0 ); + int nSuccess2 = lcl_PySlice_GetIndicesEx( rKey1.get(), nLen1, &nStart1, &nStop1, &nStep1, &nSliceLength1 ); + if ( ((nSuccess1 == -1) || (nSuccess2 == -1)) && PyErr_Occurred() ) + return nullptr; + + if ( nSliceLength0 <= 0 || nSliceLength1 <= 0 ) + { + PyErr_SetString( PyExc_KeyError, "invalid number of rows or columns" ); + return nullptr; + } + + if ( nStep0 == 1 && nStep1 == 1 ) + { + aParams = { Any(nStart1), Any(nStart0), Any(nStop1 - 1), Any(nStop0 - 1) }; + { + PyThreadDetach antiguard; + aRet = me->members->xInvocation->invoke ( + "getCellRangeByPosition", aParams, aOutParamIndex, aOutParam ); + } + PyRef rRet = runtime.any2PyObject( aRet ); + return rRet.getAcquired(); + } + + PyErr_SetString( PyExc_KeyError, "step != 1 not supported" ); + return nullptr; + } + + PyErr_SetString( PyExc_KeyError, "invalid subscript" ); + return nullptr; +} + +static PyObject* lcl_getitem_index( PyUNO const *me, PyObject *pKey, Runtime const & runtime ) +{ + Any aRet; + sal_Int32 nIndex; + + nIndex = lcl_PyNumber_AsSal_Int32( pKey ); + if (nIndex == -1 && PyErr_Occurred()) + return nullptr; + + { + PyThreadDetach antiguard; + + Reference< XIndexAccess > xIndexAccess( me->members->xInvocation, UNO_QUERY ); + if ( xIndexAccess.is() ) + { + if (nIndex < 0) + nIndex += xIndexAccess->getCount(); + aRet = xIndexAccess->getByIndex( nIndex ); + } + } + if ( aRet.hasValue() ) + { + PyRef rRet ( runtime.any2PyObject( aRet ) ); + return rRet.getAcquired(); + } + + return nullptr; +} + +static PyObject* lcl_getitem_slice( PyUNO const *me, PyObject *pKey ) +{ + Runtime runtime; + + Reference< XIndexAccess > xIndexAccess; + sal_Int32 nLen = 0; + + { + PyThreadDetach antiguard; + + xIndexAccess.set( me->members->xInvocation, UNO_QUERY ); + if ( xIndexAccess.is() ) + nLen = xIndexAccess->getCount(); + } + + if ( !xIndexAccess ) + return nullptr; + + sal_Int32 nStart = 0, nStop = 0, nStep = 0, nSliceLength = 0; + int nSuccess = lcl_PySlice_GetIndicesEx(pKey, nLen, &nStart, &nStop, &nStep, &nSliceLength); + if ( nSuccess == -1 && PyErr_Occurred() ) + return nullptr; + + PyRef rTuple( PyTuple_New( nSliceLength ), SAL_NO_ACQUIRE, NOT_NULL ); + sal_Int32 nCur, i; + for ( nCur = nStart, i = 0; i < nSliceLength; nCur += nStep, i++ ) + { + Any aRet; + + { + PyThreadDetach antiguard; + + aRet = xIndexAccess->getByIndex( nCur ); + } + PyRef rRet = runtime.any2PyObject( aRet ); + PyTuple_SetItem( rTuple.get(), i, rRet.getAcquired() ); + } + + return rTuple.getAcquired(); +} + +static PyObject* lcl_getitem_string( PyUNO const *me, PyObject *pKey, Runtime const & runtime ) +{ + OUString sKey = pyString2ustring( pKey ); + Any aRet; + + { + PyThreadDetach antiguard; + + Reference< XNameAccess > xNameAccess( me->members->xInvocation, UNO_QUERY ); + if ( xNameAccess.is() ) + { + aRet = xNameAccess->getByName( sKey ); + } + } + if ( aRet.hasValue() ) + { + PyRef rRet = runtime.any2PyObject( aRet ); + return rRet.getAcquired(); + } + + return nullptr; +} + +static PyObject* PyUNO_getitem( PyObject *self, PyObject *pKey ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + Runtime runtime; + + try + { + // XIndexAccess access by index + if ( PyIndex_Check( pKey ) ) + { + PyObject* pRet = lcl_getitem_index( me, pKey, runtime ); + if ( pRet != nullptr || PyErr_Occurred() ) + return pRet; + } + + // XIndexAccess access by slice + if ( PySlice_Check( pKey ) ) + { + PyObject* pRet = lcl_getitem_slice( me, pKey ); + if ( pRet != nullptr || PyErr_Occurred() ) + return pRet; + } + + // XNameAccess access by key + if ( PyUnicode_Check( pKey ) ) + { + PyObject* pRet = lcl_getitem_string( me, pKey, runtime ); + if ( pRet != nullptr ) + return pRet; + } + + // XCellRange/XColumnRowRange specialisation + // Uses reflection as we can't have a hard dependency on XCellRange here + bool hasXCellRange = false; + + { + PyThreadDetach antiguard; + + hasXCellRange = lcl_hasInterfaceByName( me->members->wrappedObject, "com.sun.star.table.XCellRange" ); + } + if ( hasXCellRange ) + { + return lcl_getitem_XCellRange( me, pKey ); + } + + + // If the object is an XIndexAccess and/or XNameAccess, but the + // key passed wasn't suitable, give a TypeError which specifically + // describes this + Reference< XIndexAccess > xIndexAccess( me->members->xInvocation, UNO_QUERY ); + Reference< XNameAccess > xNameAccess( me->members->xInvocation, UNO_QUERY ); + if ( xIndexAccess.is() || xNameAccess.is() ) + { + PyErr_SetString( PyExc_TypeError, "subscription with invalid type" ); + return nullptr; + } + + PyErr_SetString( PyExc_TypeError, "object is not subscriptable" ); + } + catch( const css::lang::IndexOutOfBoundsException & ) + { + PyErr_SetString( PyExc_IndexError, "index out of range" ); + } + catch( const css::container::NoSuchElementException & ) + { + PyErr_SetString( PyExc_KeyError, "key not found" ); + } + catch( const css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::WrappedTargetException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return nullptr; +} + +static int lcl_setitem_index( PyUNO const *me, PyObject *pKey, PyObject *pValue ) +{ + Runtime runtime; + + Reference< XIndexContainer > xIndexContainer; + Reference< XIndexReplace > xIndexReplace; + sal_Int32 nIndex = lcl_PyNumber_AsSal_Int32( pKey ); + if ( nIndex == -1 && PyErr_Occurred() ) + return 0; + + bool isTuple = false; + + Any aValue; + if ( pValue != nullptr ) + { + isTuple = PyTuple_Check( pValue ); + + try + { + aValue = runtime.pyObject2Any( pValue ); + } + catch ( const css::uno::RuntimeException & ) + { + // TODO pyObject2Any can't convert e.g. dicts but only throws + // RuntimeException on failure. Fixing this will require an audit of + // all the rest of PyUNO + throw css::script::CannotConvertException(); + } + } + + { + PyThreadDetach antiguard; + + xIndexContainer.set( me->members->xInvocation, UNO_QUERY ); + if ( xIndexContainer.is() ) + xIndexReplace = xIndexContainer; + else + xIndexReplace.set( me->members->xInvocation, UNO_QUERY ); + + if ( xIndexReplace.is() && nIndex < 0 ) + nIndex += xIndexReplace->getCount(); + + // XIndexReplace replace by index + if ( (pValue != nullptr) && xIndexReplace.is() ) + { + if ( isTuple ) + { + // Apply type specialisation to ensure the correct kind of sequence is passed + Type aType = xIndexReplace->getElementType(); + aValue = runtime.getImpl()->cargo->xTypeConverter->convertTo( aValue, aType ); + } + + xIndexReplace->replaceByIndex( nIndex, aValue ); + return 0; + } + + // XIndexContainer remove by index + if ( (pValue == nullptr) && xIndexContainer.is() ) + { + xIndexContainer->removeByIndex( nIndex ); + return 0; + } + } + + PyErr_SetString( PyExc_TypeError, "cannot assign to object" ); + return 1; +} + +static int lcl_setitem_slice( PyUNO const *me, PyObject *pKey, PyObject *pValue ) +{ + // XIndexContainer insert/remove/replace by slice + Runtime runtime; + + Reference< XIndexReplace > xIndexReplace; + Reference< XIndexContainer > xIndexContainer; + sal_Int32 nLen = 0; + + { + PyThreadDetach antiguard; + + xIndexContainer.set( me->members->xInvocation, UNO_QUERY ); + if ( xIndexContainer.is() ) + xIndexReplace = xIndexContainer; + else + xIndexReplace.set( me->members->xInvocation, UNO_QUERY ); + + if ( xIndexReplace.is() ) + nLen = xIndexReplace->getCount(); + } + + if ( xIndexReplace.is() ) + { + sal_Int32 nStart = 0, nStop = 0, nStep = 0, nSliceLength = 0; + int nSuccess = lcl_PySlice_GetIndicesEx( pKey, nLen, &nStart, &nStop, &nStep, &nSliceLength ); + if ( (nSuccess == -1) && PyErr_Occurred() ) + return 0; + + if ( pValue == nullptr ) + { + pValue = PyTuple_New( 0 ); + } + + if ( !PyTuple_Check (pValue) ) + { + PyErr_SetString( PyExc_TypeError, "value is not a tuple" ); + return 1; + } + + Py_ssize_t nTupleLength_ssize = PyTuple_Size( pValue ); + if ( nTupleLength_ssize > SAL_MAX_INT32 ) + { + PyErr_SetString( PyExc_ValueError, "tuple too large" ); + return 1; + } + sal_Int32 nTupleLength = static_cast<sal_Int32>(nTupleLength_ssize); + + if ( (nTupleLength != nSliceLength) && (nStep != 1) ) + { + PyErr_SetString( PyExc_ValueError, "number of items assigned must be equal" ); + return 1; + } + + if ( (nTupleLength != nSliceLength) && !xIndexContainer.is() ) + { + PyErr_SetString( PyExc_ValueError, "cannot change length" ); + return 1; + } + + sal_Int32 nCur, i; + sal_Int32 nMax = ::std::max( nSliceLength, nTupleLength ); + for ( nCur = nStart, i = 0; i < nMax; nCur += nStep, i++ ) + { + if ( i < nTupleLength ) + { + PyRef rItem = PyTuple_GetItem( pValue, i ); + bool isTuple = PyTuple_Check( rItem.get() ); + + Any aItem; + try + { + aItem = runtime.pyObject2Any( rItem.get() ); + } + catch ( const css::uno::RuntimeException & ) + { + // TODO pyObject2Any can't convert e.g. dicts but only throws + // RuntimeException on failure. Fixing this will require an audit of + // all the rest of PyUNO + throw css::script::CannotConvertException(); + } + + { + PyThreadDetach antiguard; + + if ( isTuple ) + { + // Apply type specialisation to ensure the correct kind of sequence is passed + Type aType = xIndexReplace->getElementType(); + aItem = runtime.getImpl()->cargo->xTypeConverter->convertTo( aItem, aType ); + } + + if ( i < nSliceLength ) + { + xIndexReplace->replaceByIndex( nCur, aItem ); + } + else + { + xIndexContainer->insertByIndex( nCur, aItem ); + } + } + } + else + { + PyThreadDetach antiguard; + + xIndexContainer->removeByIndex( nCur ); + nCur--; + } + } + + return 0; + } + + PyErr_SetString( PyExc_TypeError, "cannot assign to object" ); + return 1; +} + +static int lcl_setitem_string( PyUNO const *me, PyObject *pKey, PyObject *pValue ) +{ + Runtime runtime; + + OUString sKey = pyString2ustring( pKey ); + bool isTuple = false; + + Any aValue; + if ( pValue != nullptr) + { + isTuple = PyTuple_Check( pValue ); + try + { + aValue = runtime.pyObject2Any( pValue ); + } + catch( const css::uno::RuntimeException & ) + { + // TODO pyObject2Any can't convert e.g. dicts but only throws + // RuntimeException on failure. Fixing this will require an audit of + // all the rest of PyUNO + throw css::script::CannotConvertException(); + } + } + + { + PyThreadDetach antiguard; + + Reference< XNameContainer > xNameContainer( me->members->xInvocation, UNO_QUERY ); + Reference< XNameReplace > xNameReplace; + if ( xNameContainer.is() ) + xNameReplace = xNameContainer; + else + xNameReplace.set( me->members->xInvocation, UNO_QUERY ); + + if ( xNameReplace.is() ) + { + if ( isTuple && aValue.hasValue() ) + { + // Apply type specialisation to ensure the correct kind of sequence is passed + Type aType = xNameReplace->getElementType(); + aValue = runtime.getImpl()->cargo->xTypeConverter->convertTo( aValue, aType ); + } + + if ( aValue.hasValue() ) + { + if ( xNameContainer.is() ) + { + try { + xNameContainer->insertByName( sKey, aValue ); + return 0; + } + catch( const css::container::ElementExistException & ) + { + // Fall through, try replace instead + } + } + + xNameReplace->replaceByName( sKey, aValue ); + return 0; + } + else if ( xNameContainer.is() ) + { + xNameContainer->removeByName( sKey ); + return 0; + } + } + } + + PyErr_SetString( PyExc_TypeError, "cannot assign to object" ); + return 1; +} + +static int PyUNO_setitem( PyObject *self, PyObject *pKey, PyObject *pValue ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + try + { + if ( PyIndex_Check( pKey ) ) + { + return lcl_setitem_index( me, pKey, pValue ); + } + else if ( PySlice_Check( pKey ) ) + { + return lcl_setitem_slice( me, pKey, pValue ); + } + else if ( PyUnicode_Check( pKey ) ) + { + return lcl_setitem_string( me, pKey, pValue ); + } + + PyErr_SetString( PyExc_TypeError, "list index has invalid type" ); + } + catch( const css::lang::IndexOutOfBoundsException & ) + { + PyErr_SetString( PyExc_IndexError, "list index out of range" ); + } + catch( const css::container::NoSuchElementException & ) + { + PyErr_SetString( PyExc_KeyError, "key not found" ); + } + catch( const css::lang::IllegalArgumentException & ) + { + PyErr_SetString( PyExc_TypeError, "value has invalid type" ); + } + catch( const css::script::CannotConvertException & ) + { + PyErr_SetString( PyExc_TypeError, "value has invalid type" ); + } + catch( const css::container::ElementExistException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::WrappedTargetException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return 1; +} + +static PyObject* PyUNO_iter( PyObject *self ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + try + { + Reference< XEnumerationAccess > xEnumerationAccess; + Reference< XEnumeration > xEnumeration; + Reference< XIndexAccess > xIndexAccess; + Reference< XNameAccess > xNameAccess; + + { + PyThreadDetach antiguard; + + xEnumerationAccess.set( me->members->xInvocation, UNO_QUERY ); + if ( xEnumerationAccess.is() ) + xEnumeration = xEnumerationAccess->createEnumeration(); + else + xEnumeration.set( me->members->wrappedObject, UNO_QUERY ); + + if ( !xEnumeration.is() ) + xIndexAccess.set( me->members->xInvocation, UNO_QUERY ); + + if ( !xIndexAccess.is() ) + xNameAccess.set( me->members->xInvocation, UNO_QUERY ); + } + + // XEnumerationAccess iterator + // XEnumeration iterator + if (xEnumeration.is()) + { + return PyUNO_iterator_new( xEnumeration ); + } + + // XIndexAccess iterator + if ( xIndexAccess.is() ) + { + // We'd like to be able to use PySeqIter_New() here, but we're not + // allowed to because we also implement the mapping protocol + return PyUNO_list_iterator_new( xIndexAccess ); + } + + // XNameAccess iterator + if (xNameAccess.is()) + { + // There's no generic mapping iterator, but we can cobble our own + // together using PySeqIter_New() + Runtime runtime; + Any aRet; + + { + PyThreadDetach antiguard; + aRet <<= xNameAccess->getElementNames(); + } + PyRef rNames = runtime.any2PyObject( aRet ); + return PySeqIter_New( rNames.getAcquired() ); + } + + PyErr_SetString ( PyExc_TypeError, "object is not iterable" ); + } + catch( css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return nullptr; +} + +static int PyUNO_contains( PyObject *self, PyObject *pKey ) +{ + PyUNO* me = reinterpret_cast<PyUNO*>(self); + + Runtime runtime; + + try + { + Any aValue; + try + { + aValue = runtime.pyObject2Any( pKey ); + } + catch( const css::uno::RuntimeException & ) + { + // TODO pyObject2Any can't convert e.g. dicts but only throws + // RuntimeException on failure. Fixing this will require an audit of + // all the rest of PyUNO + throw css::script::CannotConvertException(); + } + + // XNameAccess is tried first, because checking key presence is much more + // useful for objects which implement both XIndexAccess and XNameAccess + + // For XNameAccess + if ( PyUnicode_Check( pKey ) ) + { + OUString sKey; + aValue >>= sKey; + Reference< XNameAccess > xNameAccess; + + { + PyThreadDetach antiguard; + + xNameAccess.set( me->members->xInvocation, UNO_QUERY ); + if ( xNameAccess.is() ) + { + bool hasKey = xNameAccess->hasByName( sKey ); + return hasKey ? 1 : 0; + } + } + } + + // For any other type of PyUNO iterable: Ugly iterative search by + // content (XIndexAccess, XEnumerationAccess, XEnumeration) + PyRef rIterator( PyUNO_iter( self ), SAL_NO_ACQUIRE ); + if ( rIterator.is() ) + { + while ( PyObject* pItem = PyIter_Next( rIterator.get() ) ) + { + PyRef rItem( pItem, SAL_NO_ACQUIRE ); + if ( PyObject_RichCompareBool( pKey, rItem.get(), Py_EQ ) == 1 ) + { + return 1; + } + } + return 0; + } + + PyErr_SetString( PyExc_TypeError, "argument is not iterable" ); + } + catch( const css::script::CannotConvertException& ) + { + PyErr_SetString( PyExc_TypeError, "invalid type passed as left argument to 'in'" ); + } + catch( const css::container::NoSuchElementException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::IndexOutOfBoundsException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::WrappedTargetException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return -1; +} + +static PyObject* PyUNO_getattr (PyObject* self, char* name) +{ + try + { + + Runtime runtime; + + PyUNO* me = reinterpret_cast<PyUNO*>(self); + if (strcmp (name, "__dict__") == 0) + { + Py_INCREF (Py_TYPE(me)->tp_dict); + return Py_TYPE(me)->tp_dict; + } + if (strcmp (name, "__class__") == 0) + { + Py_INCREF (Py_None); + return Py_None; + } + + PyObject *pRet = PyObject_GenericGetAttr( self, PyUnicode_FromString( name ) ); + if( pRet ) + return pRet; + PyErr_Clear(); + + OUString attrName( OUString::createFromAscii( name ) ); + //We need to find out if it's a method... + bool isMethod; + { + PyThreadDetach antiguard; + isMethod = me->members->xInvocation->hasMethod (attrName); + } + if (isMethod) + { + //Create a callable object to invoke this... + PyRef ret = PyUNO_callable_new ( + me->members->xInvocation, + attrName); + Py_XINCREF( ret.get() ); + return ret.get(); + + } + + //or a property + bool isProperty; + Any anyRet; + { + PyThreadDetach antiguard; + isProperty = me->members->xInvocation->hasProperty ( attrName); + if (isProperty) + { + //Return the value of the property + anyRet = me->members->xInvocation->getValue (attrName); + } + } + if (isProperty) + { + PyRef ret = runtime.any2PyObject(anyRet); + Py_XINCREF( ret.get() ); + return ret.get(); + } + + //or else... + PyErr_SetString (PyExc_AttributeError, name); + } + catch( const css::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + } + catch( const css::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + + return nullptr; +} + +static int PyUNO_setattr (PyObject* self, char* name, PyObject* value) +{ + PyUNO* me; + + me = reinterpret_cast<PyUNO*>(self); + try + { + Runtime runtime; + Any val= runtime.pyObject2Any(value, ACCEPT_UNO_ANY); + + OUString attrName( OUString::createFromAscii( name ) ); + { + PyThreadDetach antiguard; + if (me->members->xInvocation->hasProperty (attrName)) + { + me->members->xInvocation->setValue (attrName, val); + return 0; //Keep with Python's boolean system + } + } + } + catch( const css::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + return 1; + } + catch( const css::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( Any(e) ); + return 1; + } + catch( const css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( Any(e) ); + return 1; + } + catch( const RuntimeException & e ) + { + raisePyExceptionWithAny( Any( e ) ); + return 1; + } + PyErr_SetString (PyExc_AttributeError, name); + return 1; //as above. +} + +static PyObject* PyUNO_cmp( PyObject *self, PyObject *that, int op ) +{ + PyObject *result; + + if(op != Py_EQ && op != Py_NE) + { + PyErr_SetString(PyExc_TypeError, "only '==' and '!=' comparisons are defined"); + return nullptr; + } + if( self == that ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF(result); + return result; + } + try + { + Runtime runtime; + if( PyObject_IsInstance( that, getPyUnoClass().get() ) ) + { + + PyUNO *me = reinterpret_cast< PyUNO*> ( self ); + PyUNO *other = reinterpret_cast< PyUNO *> (that ); + css::uno::TypeClass tcMe = me->members->wrappedObject.getValueTypeClass(); + css::uno::TypeClass tcOther = other->members->wrappedObject.getValueTypeClass(); + + if( tcMe == tcOther ) + { + if( me->members->wrappedObject == other->members->wrappedObject ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF(result); + return result; + } + } + } + } + catch( const css::uno::RuntimeException & e) + { + raisePyExceptionWithAny( Any( e ) ); + } + + result = (op == Py_EQ ? Py_False : Py_True); + Py_INCREF(result); + return result; +} + +static PyMethodDef PyUNOMethods[] = +{ + {"__dir__", reinterpret_cast<PyCFunction>(PyUNO_dir), METH_NOARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyNumberMethods PyUNONumberMethods[] = +{ + nullptr, /* nb_add */ + nullptr, /* nb_subtract */ + nullptr, /* nb_multiply */ + nullptr, /* nb_remainder */ + nullptr, /* nb_divmod */ + nullptr, /* nb_power */ + nullptr, /* nb_negative */ + nullptr, /* nb_positive */ + nullptr, /* nb_absolute */ + PyUNO_bool, /* nb_bool */ + nullptr, /* nb_invert */ + nullptr, /* nb_lshift */ + nullptr, /* nb_rshift */ + nullptr, /* nb_and */ + nullptr, /* nb_xor */ + nullptr, /* nb_or */ + nullptr, /* nb_int */ + nullptr, /* nb_reserved */ + nullptr, /* nb_float */ + nullptr, /* nb_inplace_add */ + nullptr, /* nb_inplace_subtract */ + nullptr, /* nb_inplace_multiply */ + nullptr, /* nb_inplace_remainder */ + nullptr, /* nb_inplace_power */ + nullptr, /* nb_inplace_lshift */ + nullptr, /* nb_inplace_rshift */ + nullptr, /* nb_inplace_and */ + nullptr, /* nb_inplace_xor */ + nullptr, /* nb_inplace_or */ + + nullptr, /* nb_floor_divide */ + nullptr, /* nb_true_divide */ + nullptr, /* nb_inplace_floor_divide */ + nullptr, /* nb_inplace_true_divide */ + + nullptr, /* nb_index */ +#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 5 + nullptr, /* nb_matrix_multiply */ + nullptr, /* nb_inplace_matrix_multiply */ +#endif +}; + +static PySequenceMethods PyUNOSequenceMethods[] = +{ + nullptr, /* sq_length */ + nullptr, /* sq_concat */ + nullptr, /* sq_repeat */ + nullptr, /* sq_item */ + nullptr, /* sq_slice */ + nullptr, /* sq_ass_item */ + nullptr, /* sq_ass_slice */ + PyUNO_contains, /* sq_contains */ + nullptr, /* sq_inplace_concat */ + nullptr /* sq_inplace_repeat */ +}; + +static PyMappingMethods PyUNOMappingMethods[] = +{ + PyUNO_len, /* mp_length */ + PyUNO_getitem, /* mp_subscript */ + PyUNO_setitem, /* mp_ass_subscript */ +}; + +static PyTypeObject PyUNOType = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "pyuno", + sizeof (PyUNO), + 0, + PyUNO_del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + PyUNO_getattr, + PyUNO_setattr, + /* this type does not exist in Python 3: (cmpfunc) */ nullptr, + PyUNO_repr, + PyUNONumberMethods, + PyUNOSequenceMethods, + PyUNOMappingMethods, + PyUNO_hash, + nullptr, + PyUNO_str, + nullptr, + nullptr, + nullptr, + Py_TPFLAGS_HAVE_ITER | Py_TPFLAGS_HAVE_RICHCOMPARE | Py_TPFLAGS_HAVE_SEQUENCE_IN, + nullptr, + nullptr, + nullptr, + PyUNO_cmp, + 0, + PyUNO_iter, + nullptr, + PyUNOMethods, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + , 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +int PyUNO_initType() +{ + return PyType_Ready(&PyUNOType); +} + +PyRef getPyUnoClass() +{ + return PyRef( reinterpret_cast< PyObject * > ( &PyUNOType ) ); +} + +PyRef PyUNO_new ( + const Any &targetInterface, + const Reference<XSingleServiceFactory> &ssf ) +{ + Reference<XInvocation2> xInvocation; + + { + PyThreadDetach antiguard; + xInvocation.set( + ssf->createInstanceWithArguments( Sequence<Any>( &targetInterface, 1 ) ), css::uno::UNO_QUERY_THROW ); + + auto that = comphelper::getFromUnoTunnel<Adapter>( + xInvocation->getIntrospection()->queryAdapter(cppu::UnoType<XUnoTunnel>::get())); + if( that ) + return that->getWrappedObject(); + } + if( !Py_IsInitialized() ) + throw RuntimeException(); + + PyUNO* self = PyObject_New (PyUNO, &PyUNOType); + if (self == nullptr) + return PyRef(); // == error + self->members = new PyUNOInternals; + self->members->xInvocation = xInvocation; + self->members->wrappedObject = targetInterface; + return PyRef( reinterpret_cast<PyObject*>(self), SAL_NO_ACQUIRE ); + +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_adapter.cxx b/pyuno/source/module/pyuno_adapter.cxx new file mode 100644 index 0000000000..595e09a1f8 --- /dev/null +++ b/pyuno/source/module/pyuno_adapter.cxx @@ -0,0 +1,413 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ +#include "pyuno_impl.hxx" + +#include <o3tl/any.hxx> + +#include <com/sun/star/beans/MethodConcept.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XInvocationAdapterFactory2.hpp> +#include <com/sun/star/beans/XIntrospection.hpp> + +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <utility> + + +using com::sun::star::beans::XIntrospectionAccess; +using com::sun::star::uno::Any; +using com::sun::star::uno::Reference; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Type; +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::lang::IllegalArgumentException; +using com::sun::star::beans::UnknownPropertyException; +using com::sun::star::script::CannotConvertException; +using com::sun::star::reflection::InvocationTargetException; +using com::sun::star::reflection::XIdlMethod; +using com::sun::star::reflection::ParamInfo; + +#define TO_ASCII(x) OUStringToOString( x , RTL_TEXTENCODING_ASCII_US).getStr() + +namespace pyuno +{ + +Adapter::Adapter( PyRef ref, const Sequence< Type > &types ) + : mWrappedObject(std::move( ref )), + mInterpreter( (PyThreadState_Get()->interp) ), + mTypes( types ) +{} + +Adapter::~Adapter() +{ + // Problem: We don't know, if we have the python interpreter lock + // There is no runtime function to get to know this. + decreaseRefCount( mInterpreter, mWrappedObject.get() ); + mWrappedObject.scratch(); +} + +const Sequence<sal_Int8> & Adapter::getUnoTunnelId() +{ + static const comphelper::UnoIdInit g_id; + return g_id.getSeq(); +} + +sal_Int64 Adapter::getSomething( const Sequence< sal_Int8 > &id) +{ + return comphelper::getSomethingImpl(id, this); +} + +void raiseInvocationTargetExceptionWhenNeeded( const Runtime &runtime ) +{ + if( !Py_IsInitialized() ) + throw InvocationTargetException(); + + if( PyErr_Occurred() ) + { + PyRef excType, excValue, excTraceback; + PyErr_Fetch(reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback)); + Any unoExc( runtime.extractUnoException( excType, excValue, excTraceback ) ); + throw InvocationTargetException( + o3tl::doAccess<css::uno::Exception>(unoExc)->Message, + Reference<XInterface>(), unoExc ); + } +} + +Reference< XIntrospectionAccess > Adapter::getIntrospection() +{ + // not supported + return Reference< XIntrospectionAccess > (); +} + +Sequence< sal_Int16 > Adapter::getOutIndexes( const OUString & functionName ) +{ + Sequence< sal_Int16 > ret; + MethodOutIndexMap::const_iterator ii = m_methodOutIndexMap.find( functionName ); + if( ii == m_methodOutIndexMap.end() ) + { + + Runtime runtime; + { + PyThreadDetach antiguard; + + // retrieve the adapter object again. It will be the same instance as before, + // (the adapter factory keeps a weak map inside, which I couldn't have outside) + Reference< XInterface > unoAdapterObject = + runtime.getImpl()->cargo->xAdapterFactory->createAdapter( this, mTypes ); + + // uuuh, that's really expensive. The alternative would have been, to store + // an instance of the introspection at (this), but this results in a cyclic + // reference, which is never broken (as it is up to OOo1.1.0). + Reference< XIntrospectionAccess > introspection = + runtime.getImpl()->cargo->xIntrospection->inspect( Any( unoAdapterObject ) ); + + if( !introspection.is() ) + { + throw RuntimeException( + "pyuno bridge: Couldn't inspect uno adapter ( the python class must implement com.sun.star.lang.XTypeProvider !)" ); + } + + Reference< XIdlMethod > method = introspection->getMethod( + functionName, css::beans::MethodConcept::ALL ); + if( ! method.is( ) ) + { + throw RuntimeException( + "pyuno bridge: Couldn't get reflection for method " + functionName ); + } + + const Sequence< ParamInfo > seqInfo = method->getParameterInfos(); + std::vector<sal_Int16> retVec; + for( sal_Int32 i = 0; i < seqInfo.getLength(); ++i ) + { + if( seqInfo[i].aMode == css::reflection::ParamMode_OUT || + seqInfo[i].aMode == css::reflection::ParamMode_INOUT ) + { + retVec.push_back(static_cast<sal_Int16>(i)); + } + } + + ret = comphelper::containerToSequence(retVec); + } + // guard active again ! + m_methodOutIndexMap[ functionName ] = ret; + } + else + { + ret = ii->second; + } + return ret; +} + +Any Adapter::invoke( const OUString &aFunctionName, + const Sequence< Any >& aParams, + Sequence< sal_Int16 > &aOutParamIndex, + Sequence< Any > &aOutParam) +{ + Any ret; + + // special hack for the uno object identity concept. The XUnoTunnel.getSomething() call is + // always handled by the adapter directly. + if( aParams.getLength() == 1 && aFunctionName == "getSomething" ) + { + Sequence< sal_Int8 > id; + if( aParams[0] >>= id ) + return css::uno::Any( getSomething( id ) ); + + } + + RuntimeCargo *cargo = nullptr; + try + { + PyThreadAttach guard( mInterpreter ); + { + if( !Py_IsInitialized() ) + throw InvocationTargetException(); + + // convert parameters to python args + // TODO: Out parameter + Runtime runtime; + cargo = runtime.getImpl()->cargo; + if( isLog( cargo, LogLevel::CALL ) ) + { + logCall( cargo, "try uno->py[0x", + mWrappedObject.get(), aFunctionName, aParams ); + } + + sal_Int32 size = aParams.getLength(); + PyRef argsTuple(PyTuple_New( size ), SAL_NO_ACQUIRE, NOT_NULL ); + int i; + // fill tuple with default values in case of exceptions + for( i = 0 ;i < size ; i ++ ) + { + Py_INCREF( Py_None ); + PyTuple_SetItem( argsTuple.get(), i, Py_None ); + } + + // convert args to python + for( i = 0; i < size ; i ++ ) + { + PyRef val = runtime.any2PyObject( aParams[i] ); + + // any2PyObject() can release the GIL + if( !Py_IsInitialized() ) + throw InvocationTargetException(); + + PyTuple_SetItem( argsTuple.get(), i, val.getAcquired() ); + } + + // get callable + PyRef method(PyObject_GetAttrString( mWrappedObject.get(), TO_ASCII(aFunctionName)), + SAL_NO_ACQUIRE); + + raiseInvocationTargetExceptionWhenNeeded( runtime); + if( !method.is() ) + { + PyRef str( PyObject_Repr( mWrappedObject.get() ), SAL_NO_ACQUIRE ); + + OUString sMsg = "pyuno::Adapter: Method " + + aFunctionName + + " is not implemented at object " + + pyString2ustring(str.get()); + throw IllegalArgumentException( sMsg, Reference< XInterface > (),0 ); + } + + PyRef pyRet( PyObject_CallObject( method.get(), argsTuple.get() ), SAL_NO_ACQUIRE ); + raiseInvocationTargetExceptionWhenNeeded( runtime); + if( pyRet.is() ) + { + ret = runtime.pyObject2Any( pyRet ); + + if( ret.hasValue() && + ret.getValueTypeClass() == css::uno::TypeClass_SEQUENCE && + aFunctionName != "getTypes" && // needed by introspection itself ! + aFunctionName != "getImplementationId" ) // needed by introspection itself ! + { + // the sequence can either be + // 1) a simple sequence return value + // 2) a sequence, where the first element is the return value + // and the following elements are interpreted as the outparameter + // I can only decide for one solution by checking the method signature, + // so I need the reflection of the adapter ! + aOutParamIndex = getOutIndexes( aFunctionName ); + if( aOutParamIndex.hasElements() ) + { + // out parameters exist, extract the sequence + Sequence< Any > seq; + if( ! ( ret >>= seq ) ) + { + throw RuntimeException( + "pyuno bridge: Couldn't extract out parameters for method " + aFunctionName ); + } + + auto nOutLength = aOutParamIndex.getLength(); + if( nOutLength + 1 != seq.getLength() ) + { + OUString sMsg = "pyuno bridge: expected for method " + + aFunctionName + + " one return value and " + + OUString::number(nOutLength) + + " out parameters, got a sequence of " + + OUString::number(seq.getLength()) + + " elements as return value."; + throw RuntimeException( sMsg, *this ); + } + + aOutParam.realloc( nOutLength ); + ret = std::as_const(seq)[0]; + std::copy_n(std::next(std::cbegin(seq)), nOutLength, aOutParam.getArray()); + } + // else { sequence is a return value !} + } + } + + // log the reply, if desired + if( isLog( cargo, LogLevel::CALL ) ) + { + logReply( cargo, "success uno->py[0x" , + mWrappedObject.get(), aFunctionName, ret, aOutParam ); + } + } + + } + catch( const InvocationTargetException & e ) + { + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( + cargo, "except uno->py[0x" , + mWrappedObject.get(), aFunctionName, + e.TargetException.getValue(),e.TargetException.getValueType() ); + } + throw; + } + catch( const IllegalArgumentException & e ) + { + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( + cargo, "except uno->py[0x" , + mWrappedObject.get(), aFunctionName, &e,cppu::UnoType<decltype(e)>::get() ); + } + throw; + } + catch( const RuntimeException & e ) + { + if( cargo && isLog( cargo, LogLevel::CALL ) ) + { + logException( + cargo, "except uno->py[0x" , + mWrappedObject.get(), aFunctionName, &e,cppu::UnoType<decltype(e)>::get() ); + } + throw; + } + catch( const CannotConvertException & e ) + { + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( + cargo, "except uno->py[0x" , + mWrappedObject.get(), aFunctionName, &e,cppu::UnoType<decltype(e)>::get() ); + } + throw; + } + return ret; +} + +void Adapter::setValue( const OUString & aPropertyName, const Any & value ) +{ + if( !hasProperty( aPropertyName ) ) + { + throw UnknownPropertyException( "pyuno::Adapter: Property " + aPropertyName + " is unknown." ); + } + + PyThreadAttach guard( mInterpreter ); + try + { + if( !Py_IsInitialized() ) + throw InvocationTargetException(); + + Runtime runtime; + PyRef obj = runtime.any2PyObject( value ); + + // any2PyObject() can release the GIL + if( !Py_IsInitialized() ) + throw InvocationTargetException(); + + PyObject_SetAttrString( + mWrappedObject.get(), TO_ASCII(aPropertyName), obj.get() ); + raiseInvocationTargetExceptionWhenNeeded( runtime); + + } + catch( const IllegalArgumentException & exc ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw InvocationTargetException( exc.Message, *this, anyEx ); + } +} + +Any Adapter::getValue( const OUString & aPropertyName ) +{ + Any ret; + PyThreadAttach guard( mInterpreter ); + { + // Should probably be InvocationTargetException, but the interface doesn't allow it + if( !Py_IsInitialized() ) + throw RuntimeException(); + + Runtime runtime; + PyRef pyRef( + PyObject_GetAttrString( mWrappedObject.get(), TO_ASCII(aPropertyName) ), + SAL_NO_ACQUIRE ); + + if (!pyRef.is() || PyErr_Occurred()) + { + throw UnknownPropertyException( "pyuno::Adapter: Property " + aPropertyName + " is unknown." ); + } + ret = runtime.pyObject2Any( pyRef ); + } + return ret; +} + +sal_Bool Adapter::hasMethod( const OUString & aMethodName ) +{ + return hasProperty( aMethodName ); +} + +sal_Bool Adapter::hasProperty( const OUString & aPropertyName ) +{ + bool bRet = false; + PyThreadAttach guard( mInterpreter ); + { + // Should probably be InvocationTargetException, but the interface doesn't allow it + if( !Py_IsInitialized() ) + throw RuntimeException(); + + bRet = PyObject_HasAttrString( + mWrappedObject.get() , TO_ASCII( aPropertyName )); + } + return bRet; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_callable.cxx b/pyuno/source/module/pyuno_callable.cxx new file mode 100644 index 0000000000..f22de316b7 --- /dev/null +++ b/pyuno/source/module/pyuno_callable.cxx @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ +#include "pyuno_impl.hxx" + +#include <osl/diagnose.h> + +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XInvocation2.hpp> + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Reference; +using com::sun::star::uno::Any; +using com::sun::star::uno::RuntimeException; +using com::sun::star::script::XInvocation2; + +namespace pyuno +{ +namespace { + +struct PyUNO_callable_Internals +{ + Reference<XInvocation2> xInvocation; + OUString methodName; + ConversionMode mode; +}; + +struct PyUNO_callable +{ + PyObject_HEAD + PyUNO_callable_Internals* members; +}; + +} + +static void PyUNO_callable_del (PyObject* self) +{ + PyUNO_callable* me; + + me = reinterpret_cast<PyUNO_callable*>(self); + delete me->members; + PyObject_Del (self); +} + +static PyObject* PyUNO_callable_call( + PyObject* self, PyObject* args, SAL_UNUSED_PARAMETER PyObject*) +{ + PyUNO_callable* me; + + Sequence<short> aOutParamIndex; + Sequence<Any> aOutParam; + Sequence<Any> aParams; + Any any_params; + Any ret_value; + RuntimeCargo *cargo = nullptr; + me = reinterpret_cast<PyUNO_callable*>(self); + + PyRef ret; + try + { + Runtime runtime; + cargo = runtime.getImpl()->cargo; + any_params = runtime.pyObject2Any (args, me->members->mode); + + if (any_params.getValueTypeClass () == css::uno::TypeClass_SEQUENCE) + { + any_params >>= aParams; + } + else + { + aParams = { any_params }; + } + + { + PyThreadDetach antiguard; //python free zone + + // do some logging if desired ... + if( isLog( cargo, LogLevel::CALL ) ) + { + logCall( cargo, "try py->uno[0x", me->members->xInvocation.get(), + me->members->methodName, aParams ); + } + + // do the call + ret_value = me->members->xInvocation->invoke ( + me->members->methodName, aParams, aOutParamIndex, aOutParam); + + // log the reply, if desired + if( isLog( cargo, LogLevel::CALL ) ) + { + logReply( cargo, "success py->uno[0x", me->members->xInvocation.get(), + me->members->methodName, ret_value, aOutParam); + } + } + + + PyRef temp = runtime.any2PyObject (ret_value); + if( aOutParam.getLength() ) + { + PyRef return_list( PyTuple_New (1+aOutParam.getLength()), SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem (return_list.get(), 0, temp.getAcquired()); + + // initialize with defaults in case of exceptions + int i; + for( i = 1 ; i < 1+aOutParam.getLength() ; i ++ ) + { + Py_INCREF( Py_None ); + PyTuple_SetItem( return_list.get() , i , Py_None ); + } + + for( i = 0 ; i < aOutParam.getLength() ; i ++ ) + { + PyRef ref = runtime.any2PyObject( aOutParam[i] ); + PyTuple_SetItem (return_list.get(), 1+i, ref.getAcquired()); + } + ret = return_list; + } + else + { + ret = temp; + } + } + catch( const css::reflection::InvocationTargetException & e ) + { + + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( cargo, "except py->uno[0x", me->members->xInvocation.get() , + me->members->methodName, e.TargetException.getValue(), e.TargetException.getValueTypeRef()); + } + raisePyExceptionWithAny( e.TargetException ); + } + catch( const css::script::CannotConvertException &e ) + { + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( cargo, "error py->uno[0x", me->members->xInvocation.get() , + me->members->methodName, &e, cppu::UnoType<decltype(e)>::get().getTypeLibType()); + } + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::IllegalArgumentException &e ) + { + if( isLog( cargo, LogLevel::CALL ) ) + { + logException( cargo, "error py->uno[0x", me->members->xInvocation.get() , + me->members->methodName, &e, cppu::UnoType<decltype(e)>::get().getTypeLibType()); + } + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch (const css::uno::RuntimeException &e) + { + if( cargo && isLog( cargo, LogLevel::CALL ) ) + { + logException( cargo, "error py->uno[0x", me->members->xInvocation.get() , + me->members->methodName, &e, cppu::UnoType<decltype(e)>::get().getTypeLibType()); + } + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return ret.getAcquired(); +} + + +static PyTypeObject PyUNO_callable_Type = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "PyUNO_callable", + sizeof (PyUNO_callable), + 0, + ::pyuno::PyUNO_callable_del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + ::pyuno::PyUNO_callable_call, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + , 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +PyRef PyUNO_callable_new ( + const Reference<XInvocation2> &my_inv, + const OUString & methodName, + enum ConversionMode mode ) +{ + PyUNO_callable* self; + + OSL_ENSURE (my_inv.is(), "XInvocation must be valid"); + + self = PyObject_New (PyUNO_callable, &PyUNO_callable_Type); + if (self == nullptr) + return nullptr; //NULL == Error! + + self->members = new PyUNO_callable_Internals; + self->members->xInvocation = my_inv; + self->members->methodName = methodName; + self->members->mode = mode; + + return PyRef( reinterpret_cast<PyObject*>(self), SAL_NO_ACQUIRE ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_dlopenwrapper.c b/pyuno/source/module/pyuno_dlopenwrapper.c new file mode 100644 index 0000000000..78e6982e89 --- /dev/null +++ b/pyuno/source/module/pyuno_dlopenwrapper.c @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +/* make Python.h go first as a hack to work around _POSIX_C_SOURCE redefinition + warnings: */ +#include <Python.h> + +#include <sal/config.h> + +#include <stdlib.h> +#include <string.h> + +#if defined LINUX && !defined __USE_GNU +#define __USE_GNU +#endif +#include <dlfcn.h> + +#include <rtl/string.h> + +/* A wrapper around libpyuno.so, making sure the latter is loaded RTLD_GLOBAL + so that C++ exception handling works with old GCC versions (that determine + RTTI identity by comparing string addresses rather than string content). +*/ + +static void * load(void const * address, char const * symbol) { + Dl_info dl_info; + char * slash; + size_t len; + char * libname; + void * h; + void * func; + if (dladdr(address, &dl_info) == 0) { + abort(); + } + slash = strrchr(dl_info.dli_fname, '/'); + if (slash == NULL) { + abort(); + } + len = slash - dl_info.dli_fname + 1; + libname = malloc( + len + RTL_CONSTASCII_LENGTH(SAL_DLLPREFIX "pyuno" SAL_DLLEXTENSION) + + 1); + if (libname == NULL) { + abort(); + } + strncpy(libname, dl_info.dli_fname, len); + strcpy(libname + len, SAL_DLLPREFIX "pyuno" SAL_DLLEXTENSION); + h = dlopen(libname, RTLD_LAZY | RTLD_GLOBAL); + free(libname); + if (h == NULL) { + fprintf(stderr, "failed to load pyuno: '%s'\n", dlerror()); + abort(); + } + func = dlsym(h, symbol); + if (func == NULL) { + dlclose(h); + abort(); + } + // coverity[leaked_storage] - this is on purpose + return func; +} + +SAL_DLLPUBLIC_EXPORT PyObject * PyInit_pyuno(void) { + return + ((PyObject * (*)(void)) load((void *) &PyInit_pyuno, "PyInit_pyuno"))(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_except.cxx b/pyuno/source/module/pyuno_except.cxx new file mode 100644 index 0000000000..b833970e87 --- /dev/null +++ b/pyuno/source/module/pyuno_except.cxx @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ +#include "pyuno_impl.hxx" + +#include <typelib/typedescription.hxx> +#include <com/sun/star/script/CannotConvertException.hpp> + + +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::TypeDescription; + +namespace pyuno +{ + +void raisePyExceptionWithAny( const css::uno::Any &anyExc ) +{ + try + { + Runtime runtime; + PyRef exc = runtime.any2PyObject( anyExc ); + if( exc.is() ) + { + PyRef type( getClass( anyExc.getValueType().getTypeName(),runtime ) ); + PyErr_SetObject( type.get(), exc.get()); + } + else + { + css::uno::Exception e; + anyExc >>= e; + + OUString buf = "Couldn't convert uno exception to a python exception (" + + anyExc.getValueType().getTypeName() + ": " + e.Message + ")"; + PyErr_SetString( + PyExc_SystemError, + OUStringToOString(buf,RTL_TEXTENCODING_ASCII_US).getStr() ); + } + } + catch(const css::lang::IllegalArgumentException & e) + { + PyErr_SetString( PyExc_SystemError, + OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US).getStr() ); + } + catch(const css::script::CannotConvertException & e) + { + PyErr_SetString( PyExc_SystemError, + OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US).getStr() ); + } + catch(const RuntimeException & e) + { + PyErr_SetString( PyExc_SystemError, + OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US).getStr() ); + } +} + +/// @throws RuntimeException +static PyRef createClass( const OUString & name, const Runtime &runtime ) +{ + // assuming that this is never deleted ! + // note I don't have the knowledge how to initialize these type objects correctly ! + TypeDescription desc( name ); + if( ! desc.is() ) + { + throw RuntimeException( "pyuno.getClass: uno exception " + name + " is unknown" ); + } + + bool isStruct = desc.get()->eTypeClass == typelib_TypeClass_STRUCT; + bool isExc = desc.get()->eTypeClass == typelib_TypeClass_EXCEPTION; + bool isInterface = desc.get()->eTypeClass == typelib_TypeClass_INTERFACE; + if( !isStruct && !isExc && ! isInterface ) + { + throw RuntimeException( "pyuno.getClass: " + name + "is a " + + OUString::createFromAscii( typeClassToString( static_cast<css::uno::TypeClass>(desc.get()->eTypeClass)) ) + + ", expected EXCEPTION, STRUCT or INTERFACE" ); + } + + // retrieve base class + PyRef base; + if( isInterface ) + { + typelib_InterfaceTypeDescription *pDesc = reinterpret_cast<typelib_InterfaceTypeDescription *>(desc.get()); + if( pDesc->pBaseTypeDescription ) + { + base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime ); + } + else + { + // must be XInterface ! + } + } + else + { + typelib_CompoundTypeDescription *pDesc = reinterpret_cast<typelib_CompoundTypeDescription*>(desc.get()); + if( pDesc->pBaseTypeDescription ) + { + base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime ); + } + else + { + if( isExc ) + // we are currently creating the root UNO exception + base = PyRef(PyExc_Exception); + } + } + PyRef args( PyTuple_New( 3 ), SAL_NO_ACQUIRE, NOT_NULL ); + + PyRef pyTypeName = ustring2PyString( name /*.replace( '.', '_' )*/ ); + + PyRef bases; + if( base.is() ) + { + { // for CC, keeping ref-count being 1 + bases = PyRef( PyTuple_New( 1 ), SAL_NO_ACQUIRE ); + } + PyTuple_SetItem( bases.get(), 0 , base.getAcquired() ); + } + else + { + bases = PyRef( PyTuple_New( 0 ), SAL_NO_ACQUIRE ); + } + + PyTuple_SetItem( args.get(), 0, pyTypeName.getAcquired()); + PyTuple_SetItem( args.get(), 1, bases.getAcquired() ); + PyTuple_SetItem( args.get(), 2, PyDict_New() ); + + PyRef ret( + PyObject_CallObject(reinterpret_cast<PyObject *>(&PyType_Type) , args.get()), + SAL_NO_ACQUIRE ); + + // now overwrite ctor and attrib functions + if( isInterface ) + { + PyObject_SetAttrString( + ret.get(), "__pyunointerface__", + ustring2PyString(name).get() ); + } + else + { + PyRef ctor = getObjectFromUnoModule( runtime,"_uno_struct__init__" ); + PyRef setter = getObjectFromUnoModule( runtime,"_uno_struct__setattr__" ); + PyRef getter = getObjectFromUnoModule( runtime,"_uno_struct__getattr__" ); + PyRef repr = getObjectFromUnoModule( runtime,"_uno_struct__repr__" ); + PyRef eq = getObjectFromUnoModule( runtime,"_uno_struct__eq__" ); + PyRef ne = getObjectFromUnoModule( runtime,"_uno_struct__ne__" ); + + PyObject_SetAttrString( + ret.get(), "__pyunostruct__", + ustring2PyString(name).get() ); + PyObject_SetAttrString( + ret.get(), "typeName", + ustring2PyString(name).get() ); + PyObject_SetAttrString( + ret.get(), "__init__", ctor.get() ); + PyObject_SetAttrString( + ret.get(), "__getattr__", getter.get() ); + PyObject_SetAttrString( + ret.get(), "__setattr__", setter.get() ); + PyObject_SetAttrString( + ret.get(), "__repr__", repr.get() ); + PyObject_SetAttrString( + ret.get(), "__str__", repr.get() ); + PyObject_SetAttrString( + ret.get(), "__eq__", eq.get() ); + PyObject_SetAttrString( + ret.get(), "__ne__", ne.get() ); + } + return ret; +} + +bool isInstanceOfStructOrException( PyObject *obj) +{ + PyRef attr( + PyObject_GetAttrString(obj, "__class__"), + SAL_NO_ACQUIRE ); + if(attr.is()) + return PyObject_HasAttrString(attr.get(), "__pyunostruct__"); + else + return false; +} + +bool isInterfaceClass( const Runtime &runtime, PyObject * obj ) +{ + const ClassSet & set = runtime.getImpl()->cargo->interfaceSet; + return set.find( obj ) != set.end(); +} + +PyRef getClass( const OUString & name , const Runtime &runtime) +{ + PyRef ret; + + RuntimeCargo *cargo =runtime.getImpl()->cargo; + ExceptionClassMap::iterator ii = cargo->exceptionMap.find( name ); + if( ii == cargo->exceptionMap.end() ) + { + ret = createClass( name, runtime ); + cargo->exceptionMap[name] = ret; + if( PyObject_HasAttrString( + ret.get(), "__pyunointerface__" ) ) + cargo->interfaceSet.insert( ret ); + + PyObject_SetAttrString( + ret.get(), "__pyunointerface__", + ustring2PyString(name).get() ); + } + else + { + ret = ii->second; + } + + return ret; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_gc.cxx b/pyuno/source/module/pyuno_gc.cxx new file mode 100644 index 0000000000..1efca400d5 --- /dev/null +++ b/pyuno/source/module/pyuno_gc.cxx @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include "pyuno_impl.hxx" + +#include <sal/config.h> + +#include <rtl/ref.hxx> +#include <salhelper/thread.hxx> + +namespace pyuno +{ + +static bool g_destructorsOfStaticObjectsHaveBeenCalled; + +namespace { + +class StaticDestructorGuard +{ +public: + ~StaticDestructorGuard() + { + g_destructorsOfStaticObjectsHaveBeenCalled = true; + } +}; + +} + +static StaticDestructorGuard guard; + +static bool isAfterUnloadOrPy_Finalize() +{ + return g_destructorsOfStaticObjectsHaveBeenCalled || + !Py_IsInitialized(); +} + +namespace { + +class GCThread: public salhelper::Thread { +public: + GCThread( PyInterpreterState *interpreter, PyObject * object ); + +private: + virtual ~GCThread() override {} + + virtual void execute() override; + + PyObject *mPyObject; + PyInterpreterState *mPyInterpreter; +}; + +} + +GCThread::GCThread( PyInterpreterState *interpreter, PyObject * object ) : + Thread( "pyunoGCThread" ), mPyObject( object ), + mPyInterpreter( interpreter ) +{} + +void GCThread::execute() +{ + // otherwise we crash here, when main has been left already + if( isAfterUnloadOrPy_Finalize() ) + return; + try + { + PyThreadAttach g( mPyInterpreter ); + { + Runtime runtime; + + // remove the reference from the pythonobject2adapter map + PyRef2Adapter::iterator ii = + runtime.getImpl()->cargo->mappedObjects.find( mPyObject ); + if( ii != runtime.getImpl()->cargo->mappedObjects.end() ) + { + runtime.getImpl()->cargo->mappedObjects.erase( ii ); + } + + Py_XDECREF( mPyObject ); + } + } + catch( const css::uno::RuntimeException & e ) + { + OString msg = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US ); + fprintf( stderr, "Leaking python objects bridged to UNO for reason %s\n",msg.getStr()); + } +} + +void decreaseRefCount( PyInterpreterState *interpreter, PyObject *object ) +{ + // otherwise we crash in the last after main ... + if( isAfterUnloadOrPy_Finalize() ) + return; + + // delegate to a new thread, because there does not seem + // to be a method, which tells, whether the global + // interpreter lock is held or not + // TODO: Look for a more efficient solution + try + { + rtl::Reference< GCThread >(new GCThread(interpreter, object))->launch(); + //TODO: a protocol is missing how to join with the launched thread + // before exit(3), to ensure the thread is no longer relying on any + // infrastructure while that infrastructure is being shut down in + // atexit handlers + } + catch (std::runtime_error&) + { + // tdf#146621: Thread creation will fail on Windows with ERROR_ACCESS_DENIED + // when called at ExitProcess time; unhandled exception would hang the process + abort(); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_impl.hxx b/pyuno/source/module/pyuno_impl.hxx new file mode 100644 index 0000000000..946991c386 --- /dev/null +++ b/pyuno/source/module/pyuno_impl.hxx @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ +#pragma once + +#if defined(_MSC_VER) +// Workaround for some horrible hypot() mess +#include <math.h> +#endif + +#include <Python.h> + +//Python 3.0 and newer don't have these flags +#ifndef Py_TPFLAGS_HAVE_ITER +# define Py_TPFLAGS_HAVE_ITER 0 +#endif +#ifndef Py_TPFLAGS_HAVE_RICHCOMPARE +# define Py_TPFLAGS_HAVE_RICHCOMPARE 0 +#endif +#ifndef Py_TPFLAGS_HAVE_SEQUENCE_IN +# define Py_TPFLAGS_HAVE_SEQUENCE_IN 0 +#endif + +#include <pyuno.hxx> + +#include <string_view> +#include <unordered_map> +#include <unordered_set> + +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/script/XInvocation.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> + +#include <osl/module.hxx> + +namespace com::sun::star::beans { class XIntrospection; } +namespace com::sun::star::container { class XEnumeration; } +namespace com::sun::star::container { class XHierarchicalNameAccess; } +namespace com::sun::star::lang { class XSingleServiceFactory; } +namespace com::sun::star::reflection { class XIdlReflection; } +namespace com::sun::star::script { class XInvocation2; } +namespace com::sun::star::script { class XInvocationAdapterFactory2; } +namespace com::sun::star::script { class XTypeConverter; } + +namespace pyuno +{ + + +// Logging API - implementation can be found in pyuno_util + +struct RuntimeCargo; +namespace LogLevel +{ +// when you add a loglevel, extend the log function ! +const sal_Int32 NONE = 0; +const sal_Int32 CALL = 1; +const sal_Int32 ARGS = 2; +} + +bool isLog( RuntimeCargo const *cargo, sal_Int32 loglevel ); +void log( RuntimeCargo *cargo, sal_Int32 level, std::u16string_view logString ); +void log( RuntimeCargo *cargo, sal_Int32 level, const char *str ); +void logCall( RuntimeCargo *cargo, const char *intro, + void * ptr, std::u16string_view aFunctionName, + const css::uno::Sequence< css::uno::Any > & args ); +void logReply( RuntimeCargo *cargo, const char *intro, + void * ptr, std::u16string_view aFunctionName, + const css::uno::Any &returnValue, + const css::uno::Sequence< css::uno::Any > & args ); +void logException( RuntimeCargo *cargo, const char *intro, + void * ptr, std::u16string_view aFunctionName, + const void * data, const css::uno::Type & type ); +const sal_Int32 VAL2STR_MODE_DEEP = 0; +const sal_Int32 VAL2STR_MODE_SHALLOW = 1; +OUString val2str( const void * pVal, typelib_TypeDescriptionReference * pTypeRef, sal_Int32 mode = VAL2STR_MODE_DEEP ); + + +typedef std::unordered_map +< + PyRef, + css::uno::WeakReference< css::script::XInvocation >, + PyRef::Hash +> PyRef2Adapter; + + +typedef std::unordered_map +< + OUString, + PyRef +> ExceptionClassMap; + +typedef std::unordered_map +< + OUString, + css::uno::Sequence< sal_Int16 > +> MethodOutIndexMap; + +typedef std::unordered_set< PyRef , PyRef::Hash > ClassSet; + +int PyUNO_initType(); +int PyUNOStruct_initType(); + +PyRef PyUNO_new ( + const css::uno::Any & targetInterface, + const css::uno::Reference<css::lang::XSingleServiceFactory> & ssf ); + +PyRef PyUNOStruct_new ( + const css::uno::Any &targetInterface, + const css::uno::Reference<css::lang::XSingleServiceFactory> &ssf ); + +struct PyUNOInternals +{ + css::uno::Reference <css::script::XInvocation2> xInvocation; + css::uno::Any wrappedObject; +}; + +typedef struct +{ + PyObject_HEAD + PyUNOInternals* members; +} PyUNO; + +PyObject* PyUNO_iterator_new ( + const css::uno::Reference<css::container::XEnumeration>& xEnumeration); + +struct PyUNO_iterator_Internals +{ + css::uno::Reference <css::container::XEnumeration> xEnumeration; +}; + +typedef struct +{ + PyObject_HEAD + PyUNO_iterator_Internals* members; +} PyUNO_iterator; + +PyObject* PyUNO_list_iterator_new ( + const css::uno::Reference<css::container::XIndexAccess> &xIndexAccess); + +struct PyUNO_list_iterator_Internals +{ + css::uno::Reference <css::container::XIndexAccess> xIndexAccess; + int index; +}; + +typedef struct +{ + PyObject_HEAD + PyUNO_list_iterator_Internals* members; +} PyUNO_list_iterator; + +PyRef ustring2PyUnicode( const OUString &source ); +PyRef ustring2PyString( std::u16string_view source ); +OUString pyString2ustring( PyObject *str ); + +/// @throws css::reflection::InvocationTargetException +/// @throws css::uno::RuntimeException +void raiseInvocationTargetExceptionWhenNeeded( const Runtime &runtime ); + +PyRef PyUNO_callable_new ( + const css::uno::Reference<css::script::XInvocation2> &xInv, + const OUString &methodName, + ConversionMode mode = REJECT_UNO_ANY ); + +PyObject* PyUNO_Type_new (const char *typeName , css::uno::TypeClass t , const Runtime &r ); +PyObject* PyUNO_Enum_new( const char *enumBase, const char *enumValue, const Runtime &r ); +PyObject* PyUNO_char_new (sal_Unicode c , const Runtime &r); +PyObject *PyUNO_ByteSequence_new( const css::uno::Sequence< sal_Int8 > &, const Runtime &r ); + +PyRef getTypeClass( const Runtime &); +PyRef getEnumClass( const Runtime &); +PyRef getCharClass( const Runtime &); +PyRef getByteSequenceClass( const Runtime & ); +PyRef getPyUnoClass(); +PyRef getPyUnoStructClass(); +PyRef getClass( const OUString & name , const Runtime & runtime ); +PyRef getAnyClass( const Runtime &); +PyObject *PyUNO_invoke( PyObject *object, const char *name , PyObject *args ); + +/// @throws css::uno::RuntimeException +css::uno::Any PyEnum2Enum( PyObject *obj ); +/// @throws css::uno::RuntimeException +sal_Unicode PyChar2Unicode( PyObject *o ); +/// @throws css::uno::RuntimeException +css::uno::Type PyType2Type( PyObject * o ); + +void raisePyExceptionWithAny( const css::uno::Any &a ); +const char *typeClassToString( css::uno::TypeClass t ); + +/// @throws css::uno::RuntimeException +PyRef getObjectFromUnoModule( const Runtime &runtime, const char * object ); + +bool isInterfaceClass( const Runtime &, PyObject *obj ); +bool isInstanceOfStructOrException( PyObject *obj); + +struct RuntimeCargo +{ + css::uno::Reference< css::lang::XSingleServiceFactory > xInvocation; + css::uno::Reference< css::script::XTypeConverter> xTypeConverter; + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::reflection::XIdlReflection > xCoreReflection; + css::uno::Reference< css::container::XHierarchicalNameAccess > xTdMgr; + css::uno::Reference< css::script::XInvocationAdapterFactory2 > xAdapterFactory; + css::uno::Reference< css::beans::XIntrospection > xIntrospection; + PyRef dictUnoModule; + osl::Module testModule; + bool valid; + ExceptionClassMap exceptionMap; + ClassSet interfaceSet; + PyRef2Adapter mappedObjects; + FILE *logFile; + sal_Int32 logLevel; + + PyRef const & getUnoModule(); +}; + +struct stRuntimeImpl +{ + PyObject_HEAD + struct RuntimeCargo *cargo; +public: + static void del( PyObject *self ); + + /// @throws css::uno::RuntimeException + static PyRef create( + const css::uno::Reference< css::uno::XComponentContext > & xContext ); +}; + + +class Adapter : public cppu::WeakImplHelper< + css::script::XInvocation, css::lang::XUnoTunnel > +{ + PyRef mWrappedObject; + PyInterpreterState *mInterpreter; // interpreters don't seem to be refcounted ! + css::uno::Sequence< css::uno::Type > mTypes; + MethodOutIndexMap m_methodOutIndexMap; + +private: + css::uno::Sequence< sal_Int16 > getOutIndexes( const OUString & functionName ); + +public: +public: + Adapter( PyRef obj, + const css::uno::Sequence< css::uno::Type > & types ); + + static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); + const PyRef& getWrappedObject() const { return mWrappedObject; } + const css::uno::Sequence< css::uno::Type >& getWrappedTypes() const { return mTypes; } + virtual ~Adapter() override; + + // XInvocation + virtual css::uno::Reference< css::beans::XIntrospectionAccess > + SAL_CALL getIntrospection( ) override; + virtual css::uno::Any SAL_CALL invoke( + const OUString& aFunctionName, + const css::uno::Sequence< css::uno::Any >& aParams, + css::uno::Sequence< sal_Int16 >& aOutParamIndex, + css::uno::Sequence< css::uno::Any >& aOutParam ) override; + + virtual void SAL_CALL setValue( + const OUString& aPropertyName, + const css::uno::Any& aValue ) override; + + virtual css::uno::Any SAL_CALL getValue( const OUString& aPropertyName ) override; + virtual sal_Bool SAL_CALL hasMethod( const OUString& aName ) override; + virtual sal_Bool SAL_CALL hasProperty( const OUString& aName ) override; + + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; +}; + + +/** releases a refcount on the interpreter object and on another given python object. + + The function can be called from any thread regardless of whether the global + interpreter lock is held. + + */ +void decreaseRefCount( PyInterpreterState *interpreter, PyObject *object ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_iterator.cxx b/pyuno/source/module/pyuno_iterator.cxx new file mode 100644 index 0000000000..134f318a10 --- /dev/null +++ b/pyuno/source/module/pyuno_iterator.cxx @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <sal/config.h> + +#include "pyuno_impl.hxx" + +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/WrappedTargetException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> + +using com::sun::star::container::XEnumeration; +using com::sun::star::container::XIndexAccess; +using com::sun::star::lang::IndexOutOfBoundsException; +using com::sun::star::lang::WrappedTargetException; +using com::sun::star::uno::Any; +using com::sun::star::uno::Reference; +using com::sun::star::uno::RuntimeException; + + +namespace pyuno +{ + +static void PyUNO_iterator_del( PyObject* self ) +{ + PyUNO_iterator* me = reinterpret_cast<PyUNO_iterator*>(self); + + { + PyThreadDetach antiguard; + delete me->members; + } + PyObject_Del( self ); +} + +static PyObject* PyUNO_iterator_iter( PyObject *self ) +{ + Py_INCREF( self ); + return self; +} + +static PyObject* PyUNO_iterator_next( PyObject *self ) +{ + PyUNO_iterator* me = reinterpret_cast<PyUNO_iterator*>(self); + + Runtime runtime; + Any aRet; + + try + { + bool hasMoreElements = false; + + { + PyThreadDetach antiguard; + + hasMoreElements = me->members->xEnumeration->hasMoreElements(); + if ( hasMoreElements ) + { + aRet = me->members->xEnumeration->nextElement(); + } + } + + if ( hasMoreElements ) + { + PyRef rRet = runtime.any2PyObject( aRet ); + return rRet.getAcquired(); + } + + PyErr_SetString( PyExc_StopIteration, "" ); + return nullptr; + } + catch( css::container::NoSuchElementException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::WrappedTargetException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return nullptr; +} + +static PyTypeObject PyUNO_iterator_Type = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "PyUNO_iterator", + sizeof (PyUNO_iterator), + 0, + PyUNO_iterator_del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + Py_TPFLAGS_HAVE_ITER, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + PyUNO_iterator_iter, // Generic, reused between the iterator types + PyUNO_iterator_next, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +PyObject* PyUNO_iterator_new( const Reference< XEnumeration >& xEnumeration ) +{ + PyUNO_iterator* self = PyObject_New( PyUNO_iterator, &PyUNO_iterator_Type ); + if ( self == nullptr ) + return nullptr; // == error + self->members = new PyUNO_iterator_Internals; + self->members->xEnumeration = xEnumeration; + return reinterpret_cast<PyObject*>(self); +} + +/////////////////////////////////////////////////////////////////////////////// + +static void PyUNO_list_iterator_del( PyObject* self ) +{ + PyUNO_list_iterator* me = reinterpret_cast<PyUNO_list_iterator*>(self); + + { + PyThreadDetach antiguard; + delete me->members; + } + PyObject_Del( self ); +} + + +static PyObject* PyUNO_list_iterator_next( PyObject *self ) +{ + PyUNO_list_iterator* me = reinterpret_cast<PyUNO_list_iterator*>(self); + + Runtime runtime; + Any aRet; + + try + { + bool noMoreElements = false; + { + PyThreadDetach antiguard; + try { + aRet = me->members->xIndexAccess->getByIndex( me->members->index ); + } + catch( const css::lang::IndexOutOfBoundsException & ) + { + noMoreElements = true; + } + } + + if ( noMoreElements ) + { + PyErr_SetString( PyExc_StopIteration, "" ); + return nullptr; + } + + PyRef rRet = runtime.any2PyObject( aRet ); + me->members->index++; + return rRet.getAcquired(); + } + catch( css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::lang::WrappedTargetException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + catch( const css::uno::RuntimeException &e ) + { + raisePyExceptionWithAny( css::uno::Any( e ) ); + } + + return nullptr; +} + +static PyTypeObject PyUNO_list_iterator_Type = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "PyUNO_iterator", + sizeof (PyUNO_list_iterator), + 0, + PyUNO_list_iterator_del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + Py_TPFLAGS_HAVE_ITER, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + PyUNO_iterator_iter, // Generic, reused between the iterator types + PyUNO_list_iterator_next, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +PyObject* PyUNO_list_iterator_new( const Reference<XIndexAccess> &xIndexAccess ) +{ + PyUNO_list_iterator* self = PyObject_New( PyUNO_list_iterator, &PyUNO_list_iterator_Type ); + if ( self == nullptr ) + return nullptr; // == error + self->members = new PyUNO_list_iterator_Internals; + self->members->xIndexAccess = xIndexAccess; + self->members->index = 0; + return reinterpret_cast<PyObject*>(self); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_module.cxx b/pyuno/source/module/pyuno_module.cxx new file mode 100644 index 0000000000..793aac834b --- /dev/null +++ b/pyuno/source/module/pyuno_module.cxx @@ -0,0 +1,896 @@ +/* -*- Mode: C++; eval:(c-set-style "bsd"); tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <config_folders.h> + +#include "pyuno_impl.hxx" + +#include <cassert> +#include <string_view> +#include <unordered_map> + +#include <osl/module.hxx> +#include <osl/thread.h> +#include <osl/file.hxx> +#include <sal/log.hxx> + +#include <typelib/typedescription.hxx> + +#include <rtl/ustring.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/uuid.h> +#include <rtl/bootstrap.hxx> + +#include <uno/current_context.hxx> +#include <cppuhelper/bootstrap.hxx> + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/reflection/XConstantTypeDescription.hpp> +#include <com/sun/star/reflection/XIdlClass.hpp> +#include <com/sun/star/registry/InvalidRegistryException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/script/XInvocation2.hpp> +#include <com/sun/star/reflection/XIdlReflection.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> + +using osl::Module; + + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Reference; +using com::sun::star::uno::Any; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::TypeDescription; +using com::sun::star::uno::XComponentContext; +using com::sun::star::container::NoSuchElementException; +using com::sun::star::reflection::XIdlClass; +using com::sun::star::script::XInvocation2; + +using namespace pyuno; + +namespace { + +/** + @ index of the next to be used member in the initializer list ! + */ +// LEM TODO: export member names as keyword arguments in initialiser? +// Python supports very flexible variadic functions. By marking +// variables with one asterisk (e.g. *var) the given variable is +// defined to be a tuple of all the extra arguments. By marking +// variables with two asterisks (e.g. **var) the given variable is a +// dictionary of all extra keyword arguments; the keys are strings, +// which are the names that were used to identify the arguments. If +// they exist, these arguments must be the last one in the list. + +class fillStructState +{ + // Keyword arguments used + PyObject *used; + // Which structure members are initialised + std::unordered_map <OUString, bool> initialised; + // How many positional arguments are consumed + // This is always the so-many first ones + sal_Int32 nPosConsumed; + +public: + fillStructState() + : used (PyDict_New()) + , nPosConsumed (0) + { + if ( ! used ) + throw RuntimeException("pyuno._createUnoStructHelper failed to create new dictionary"); + } + ~fillStructState() + { + Py_DECREF(used); + } + void setUsed(PyObject *key) + { + PyDict_SetItem(used, key, Py_True); + } + void setInitialised(const OUString& key, sal_Int32 pos = -1) + { + if (initialised[key]) + { + OUStringBuffer buf( "pyuno._createUnoStructHelper: member '" + key + "'"); + if ( pos >= 0 ) + { + buf.append( " at position " + OUString::number(pos)); + } + buf.append( " initialised multiple times."); + throw RuntimeException(buf.makeStringAndClear()); + } + initialised[key] = true; + if ( pos >= 0 ) + ++nPosConsumed; + } + bool isInitialised(const OUString& key) + { + return initialised[key]; + } + PyObject *getUsed() const + { + return used; + } + sal_Int32 getCntConsumed() const + { + return nPosConsumed; + } +}; + +/// @throws RuntimeException +void fillStruct( + const Reference< XInvocation2 > &inv, + typelib_CompoundTypeDescription *pCompType, + PyObject *initializer, + PyObject *kwinitializer, + fillStructState &state, + const Runtime &runtime) +{ + if( pCompType->pBaseTypeDescription ) + fillStruct( inv, pCompType->pBaseTypeDescription, initializer, kwinitializer, state, runtime ); + + const sal_Int32 nMembers = pCompType->nMembers; + { + for( int i = 0 ; i < nMembers ; i ++ ) + { + const OUString OUMemberName (pCompType->ppMemberNames[i]); + PyObject *pyMemberName = + PyUnicode_FromString(OUStringToOString(OUMemberName, + RTL_TEXTENCODING_UTF8).getStr()); + if ( PyObject *element = PyDict_GetItem(kwinitializer, pyMemberName ) ) + { + state.setInitialised(OUMemberName); + state.setUsed(pyMemberName); + Any a = runtime.pyObject2Any( element, ACCEPT_UNO_ANY ); + inv->setValue( OUMemberName, a ); + } + } + } + { + const int remainingPosInitialisers = PyTuple_Size(initializer) - state.getCntConsumed(); + for( int i = 0 ; i < remainingPosInitialisers && i < nMembers ; i ++ ) + { + const int tupleIndex = state.getCntConsumed(); + const OUString& rMemberName (pCompType->ppMemberNames[i]); + state.setInitialised(rMemberName, tupleIndex); + PyObject *element = PyTuple_GetItem( initializer, tupleIndex ); + Any a = runtime.pyObject2Any( element, ACCEPT_UNO_ANY ); + inv->setValue( rMemberName, a ); + } + } + if ( PyTuple_Size( initializer ) <= 0 ) + return; + + // Allow partial initialisation when only keyword arguments are given + for ( int i = 0; i < nMembers ; ++i) + { + const OUString memberName (pCompType->ppMemberNames[i]); + if ( ! state.isInitialised( memberName ) ) + { + OUString buf = "pyuno._createUnoStructHelper: member '" + + memberName + + "' of struct type '" + + OUString::unacquired(&pCompType->aBase.pTypeName) + + "' not given a value."; + throw RuntimeException(buf); + } + } +} + +OUString getLibDir() +{ + static OUString sLibDir = []() { + OUString libDir; + + // workarounds the $(ORIGIN) until it is available + if (Module::getUrlFromAddress(reinterpret_cast<oslGenericFunction>(getLibDir), libDir)) + { + libDir = libDir.copy(0, libDir.lastIndexOf('/')); + OUString name("PYUNOLIBDIR"); + rtl_bootstrap_set(name.pData, libDir.pData); + } + return libDir; + }(); + + return sLibDir; +} + +void raisePySystemException( const char * exceptionType, std::u16string_view message ) +{ + OString buf = OString::Concat("Error during bootstrapping uno (") + + exceptionType + + "):" + + OUStringToOString( message, osl_getThreadTextEncoding() ); + PyErr_SetString( PyExc_SystemError, buf.getStr() ); +} + +extern "C" { + +static PyObject* getComponentContext( + SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*) +{ + PyRef ret; + try + { + Reference<XComponentContext> ctx; + + // getLibDir() must be called in order to set bootstrap variables correctly ! + OUString path( getLibDir()); + if( Runtime::isInitialized() ) + { + Runtime runtime; + ctx = runtime.getImpl()->cargo->xContext; + } + else + { + if( path.isEmpty() ) + { + PyErr_SetString( + PyExc_RuntimeError, "osl_getUrlFromAddress fails, that's why I cannot find ini " + "file for bootstrapping python uno bridge\n" ); + return nullptr; + } + + OUString iniFile = path + +#ifdef MACOSX + "/../" LIBO_ETC_FOLDER +#endif + "/" SAL_CONFIGFILE( "pyuno" ); + osl::DirectoryItem item; + if( osl::DirectoryItem::get( iniFile, item ) == osl::FileBase::E_None ) + { + // in case pyuno.ini exists, use this file for bootstrapping + PyThreadDetach antiguard; + ctx = cppu::defaultBootstrap_InitialComponentContext (iniFile); + } + else + { + // defaulting to the standard bootstrapping + PyThreadDetach antiguard; + ctx = cppu::defaultBootstrap_InitialComponentContext (); + } + + } + + if( ! Runtime::isInitialized() ) + { + Runtime::initialize( ctx ); + } + Runtime runtime; + ret = runtime.any2PyObject( Any( ctx ) ); + } + catch (const css::registry::InvalidRegistryException &e) + { + // can't use raisePyExceptionWithAny() here, because the function + // does any conversions, which will not work with a + // wrongly bootstrapped pyuno! + raisePySystemException( "InvalidRegistryException", e.Message ); + } + catch(const css::lang::IllegalArgumentException & e) + { + raisePySystemException( "IllegalArgumentException", e.Message ); + } + catch(const css::script::CannotConvertException & e) + { + raisePySystemException( "CannotConvertException", e.Message ); + } + catch (const css::uno::RuntimeException & e) + { + raisePySystemException( "RuntimeException", e.Message ); + } + catch (const css::uno::Exception & e) + { + raisePySystemException( "uno::Exception", e.Message ); + } + return ret.getAcquired(); +} + +// While pyuno.private_initTestEnvironment is called from individual Python tests (e.g., from +// UnoInProcess in unotest/source/python/org/libreoffice/unotest.py, which makes sure to call it +// only once), pyuno.private_deinitTestEnvironment is called centrally from +// unotest/source/python/org/libreoffice/unittest.py at the end of every PythonTest (to DeInitVCL +// exactly once near the end of the process, if InitVCL has ever been called via +// pyuno.private_initTestEnvironment): + +osl::Module * testModule = nullptr; + +static PyObject* initTestEnvironment( + SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*) +{ + // this tries to bootstrap enough of the soffice from python to run + // unit tests, which is only possible indirectly because pyuno is URE + // so load "test" library and invoke a function there to do the work + assert(testModule == nullptr); + try + { + PyObject *const ctx(getComponentContext(nullptr, nullptr)); + if (!ctx) { abort(); } + Runtime const runtime; + Any const a(runtime.pyObject2Any(ctx)); + Reference<XComponentContext> xContext; + a >>= xContext; + if (!xContext.is()) { abort(); } + using css::lang::XMultiServiceFactory; + Reference<XMultiServiceFactory> const xMSF( + xContext->getServiceManager(), + css::uno::UNO_QUERY_THROW); + char *const testlib = getenv("TEST_LIB"); + if (!testlib) { abort(); } +#ifdef _WIN32 + OString const libname = OString(testlib, strlen(testlib)) + .replaceAll(OString('/'), OString('\\')); +#else + OString const libname(testlib, strlen(testlib)); +#endif + + osl::Module &mod = runtime.getImpl()->cargo->testModule; + mod.load(OStringToOUString(libname, osl_getThreadTextEncoding()), + SAL_LOADMODULE_LAZY | SAL_LOADMODULE_GLOBAL); + if (!mod.is()) { abort(); } + oslGenericFunction const pFunc( + mod.getFunctionSymbol("test_init")); + if (!pFunc) { abort(); } + reinterpret_cast<void (SAL_CALL *)(XMultiServiceFactory*)>(pFunc)(xMSF.get()); + testModule = &mod; + } + catch (const css::uno::Exception &) + { + abort(); + } + return Py_None; +} + +static PyObject* deinitTestEnvironment( + SAL_UNUSED_PARAMETER PyObject*, SAL_UNUSED_PARAMETER PyObject*) +{ + if (testModule != nullptr) + { + try + { + oslGenericFunction const pFunc( + testModule->getFunctionSymbol("test_deinit")); + if (!pFunc) { abort(); } + reinterpret_cast<void (SAL_CALL *)()>(pFunc)(); + } + catch (const css::uno::Exception &) + { + abort(); + } + } + return Py_None; +} + +PyObject * extractOneStringArg( PyObject *args, char const *funcName ) +{ + if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 ) + { + OString buf = funcName + OString::Concat(": expecting one string argument"); + PyErr_SetString( PyExc_RuntimeError, buf.getStr() ); + return nullptr; + } + PyObject *obj = PyTuple_GetItem( args, 0 ); + if (!PyUnicode_Check(obj)) + { + OString buf = funcName + OString::Concat(": expecting one string argument"); + PyErr_SetString( PyExc_TypeError, buf.getStr()); + return nullptr; + } + return obj; +} + +static PyObject *createUnoStructHelper( + SAL_UNUSED_PARAMETER PyObject *, PyObject* args, PyObject* keywordArgs) +{ + Any IdlStruct; + PyRef ret; + try + { + Runtime runtime; + if( PyTuple_Size( args ) == 2 ) + { + PyObject *structName = PyTuple_GetItem(args, 0); + PyObject *initializer = PyTuple_GetItem(args, 1); + + if (PyUnicode_Check(structName)) + { + if( PyTuple_Check( initializer ) && PyDict_Check ( keywordArgs ) ) + { + OUString typeName( OUString::createFromAscii(PyUnicode_AsUTF8(structName))); + RuntimeCargo *c = runtime.getImpl()->cargo; + Reference<XIdlClass> idl_class = c->xCoreReflection->forName (typeName); + if (idl_class.is ()) + { + idl_class->createObject (IdlStruct); + PyRef returnCandidate( PyUNOStruct_new( IdlStruct, c->xInvocation ) ); + PyUNO *me = reinterpret_cast<PyUNO*>( returnCandidate.get() ); + TypeDescription desc( typeName ); + OSL_ASSERT( desc.is() ); // could already instantiate an XInvocation2 ! + + typelib_CompoundTypeDescription *pCompType = + reinterpret_cast<typelib_CompoundTypeDescription *>(desc.get()); + fillStructState state; + if ( PyTuple_Size( initializer ) > 0 || PyDict_Size( keywordArgs ) > 0 ) + fillStruct( me->members->xInvocation, pCompType, initializer, keywordArgs, state, runtime ); + if( state.getCntConsumed() != PyTuple_Size(initializer) ) + { + throw RuntimeException( "pyuno._createUnoStructHelper: too many " + "elements in the initializer list, expected " + + OUString::number(state.getCntConsumed()) + ", got " + + OUString::number( PyTuple_Size(initializer) ) ); + } + ret = PyRef( PyTuple_Pack(2, returnCandidate.get(), state.getUsed()), SAL_NO_ACQUIRE); + } + else + { + OString buf = OString::Concat("UNO struct ") + + PyUnicode_AsUTF8(structName) + + " is unknown"; + PyErr_SetString (PyExc_RuntimeError, buf.getStr()); + } + } + else + { + PyErr_SetString( + PyExc_RuntimeError, + "pyuno._createUnoStructHelper: 2nd argument (initializer sequence) is no tuple" ); + } + } + else + { + PyErr_SetString (PyExc_AttributeError, "createUnoStruct: first argument wasn't a string"); + } + } + else + { + PyErr_SetString (PyExc_AttributeError, "pyuno._createUnoStructHelper: expects exactly two non-keyword arguments:\n\tStructure Name\n\tinitialiser tuple; may be the empty tuple"); + } + } + catch( const css::uno::RuntimeException & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + catch( const css::script::CannotConvertException & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + catch( const css::uno::Exception & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + return ret.getAcquired(); +} + +static PyObject *getTypeByName( + SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + PyObject * ret = nullptr; + + try + { + char *name; + + if (PyArg_ParseTuple (args, "s", &name)) + { + OUString typeName ( OUString::createFromAscii( name ) ); + TypeDescription typeDesc( typeName ); + if( typeDesc.is() ) + { + Runtime runtime; + ret = PyUNO_Type_new( + name, static_cast<css::uno::TypeClass>(typeDesc.get()->eTypeClass), runtime ); + } + else + { + OString buf = OString::Concat("Type ") + name + " is unknown"; + PyErr_SetString( PyExc_RuntimeError, buf.getStr() ); + } + } + } + catch ( const RuntimeException & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + return ret; +} + +static PyObject *getConstantByName( + SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + PyObject *ret = nullptr; + try + { + char *name; + + if (PyArg_ParseTuple (args, "s", &name)) + { + OUString typeName ( OUString::createFromAscii( name ) ); + Runtime runtime; + css::uno::Reference< css::reflection::XConstantTypeDescription > td; + if (!(runtime.getImpl()->cargo->xTdMgr->getByHierarchicalName( + typeName) + >>= td)) + { + throw RuntimeException( "pyuno.getConstantByName: " + typeName + "is not a constant" ); + } + PyRef constant = runtime.any2PyObject( td->getConstantValue() ); + ret = constant.getAcquired(); + } + } + catch( const NoSuchElementException & e ) + { + // to the python programmer, this is a runtime exception, + // do not support tweakings with the type system + RuntimeException runExc( e.Message ); + raisePyExceptionWithAny( Any( runExc ) ); + } + catch(const css::script::CannotConvertException & e) + { + raisePyExceptionWithAny( Any( e ) ); + } + catch(const css::lang::IllegalArgumentException & e) + { + raisePyExceptionWithAny( Any( e ) ); + } + catch( const RuntimeException & e ) + { + raisePyExceptionWithAny( Any(e) ); + } + return ret; +} + +static PyObject *checkType( SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 ) + { + OString buf = "pyuno.checkType : expecting one uno.Type argument"_ostr; + PyErr_SetString( PyExc_RuntimeError, buf.getStr() ); + return nullptr; + } + PyObject *obj = PyTuple_GetItem( args, 0 ); + + try + { + PyType2Type( obj ); + } + catch(const RuntimeException & e) + { + raisePyExceptionWithAny( Any( e ) ); + return nullptr; + } + Py_INCREF( Py_None ); + return Py_None; +} + +static PyObject *checkEnum( SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 ) + { + OString buf = "pyuno.checkType : expecting one uno.Type argument"_ostr; + PyErr_SetString( PyExc_RuntimeError, buf.getStr() ); + return nullptr; + } + PyObject *obj = PyTuple_GetItem( args, 0 ); + + try + { + PyEnum2Enum( obj ); + } + catch(const RuntimeException & e) + { + raisePyExceptionWithAny( Any( e) ); + return nullptr; + } + Py_INCREF( Py_None ); + return Py_None; +} + +static PyObject *getClass( SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + PyObject *obj = extractOneStringArg( args, "pyuno.getClass"); + if( ! obj ) + return nullptr; + + try + { + Runtime runtime; + PyRef ret = getClass(pyString2ustring(obj), runtime); + Py_XINCREF( ret.get() ); + return ret.get(); + } + catch(const RuntimeException & e) + { + raisePyExceptionWithAny( Any(e) ); + } + return nullptr; +} + +static PyObject *isInterface( SAL_UNUSED_PARAMETER PyObject *, PyObject *args ) +{ + + if( PyTuple_Check( args ) && PyTuple_Size( args ) == 1 ) + { + PyObject *obj = PyTuple_GetItem( args, 0 ); + Runtime r; + return PyLong_FromLong( isInterfaceClass( r, obj ) ); + } + return PyLong_FromLong( 0 ); +} + +static PyObject * generateUuid( + SAL_UNUSED_PARAMETER PyObject *, SAL_UNUSED_PARAMETER PyObject * ) +{ + Sequence< sal_Int8 > seq( 16 ); + rtl_createUuid( reinterpret_cast<sal_uInt8*>(seq.getArray()) , nullptr , false ); + PyRef ret; + try + { + Runtime runtime; + ret = runtime.any2PyObject( Any( seq ) ); + } + catch( const RuntimeException & e ) + { + raisePyExceptionWithAny( Any(e) ); + } + return ret.getAcquired(); +} + +static PyObject *systemPathToFileUrl( + SAL_UNUSED_PARAMETER PyObject *, PyObject * args ) +{ + PyObject *obj = extractOneStringArg( args, "pyuno.systemPathToFileUrl" ); + if( ! obj ) + return nullptr; + + OUString sysPath = pyString2ustring( obj ); + OUString url; + osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath( sysPath, url ); + + if( e != osl::FileBase::E_None ) + { + OUString buf = "Couldn't convert " + + sysPath + + " to a file url for reason (" + + OUString::number( static_cast<sal_Int32>(e) ) + + ")"; + raisePyExceptionWithAny( + Any( RuntimeException( buf ))); + return nullptr; + } + return ustring2PyUnicode( url ).getAcquired(); +} + +static PyObject * fileUrlToSystemPath( + SAL_UNUSED_PARAMETER PyObject *, PyObject * args ) +{ + PyObject *obj = extractOneStringArg( args, "pyuno.fileUrlToSystemPath" ); + if( ! obj ) + return nullptr; + + OUString url = pyString2ustring( obj ); + OUString sysPath; + osl::FileBase::RC e = osl::FileBase::getSystemPathFromFileURL( url, sysPath ); + + if( e != osl::FileBase::E_None ) + { + OUString buf = "Couldn't convert file url " + + sysPath + + " to a system path for reason (" + + OUString::number( static_cast<sal_Int32>(e) ) + + ")"; + raisePyExceptionWithAny( + Any( RuntimeException( buf ))); + return nullptr; + } + return ustring2PyUnicode( sysPath ).getAcquired(); +} + +static PyObject * absolutize( SAL_UNUSED_PARAMETER PyObject *, PyObject * args ) +{ + if( !PyTuple_Check( args ) || PyTuple_Size( args ) != 2 ) + return nullptr; + + OUString ouPath = pyString2ustring( PyTuple_GetItem( args , 0 ) ); + OUString ouRel = pyString2ustring( PyTuple_GetItem( args, 1 ) ); + OUString ret; + oslFileError e = osl_getAbsoluteFileURL( ouPath.pData, ouRel.pData, &(ret.pData) ); + if( e != osl_File_E_None ) + { + OUString buf = + "Couldn't absolutize " + + ouRel + + " using root " + + ouPath + + " for reason (" + + OUString::number(static_cast<sal_Int32>(e) ) + + ")"; + + PyErr_SetString( + PyExc_OSError, + OUStringToOString(buf,osl_getThreadTextEncoding()).getStr()); + return nullptr; + } + return ustring2PyUnicode( ret ).getAcquired(); +} + +static PyObject * invoke(SAL_UNUSED_PARAMETER PyObject *, PyObject *args) +{ + PyObject *ret = nullptr; + if(PyTuple_Check(args) && PyTuple_Size(args) == 3) + { + PyObject *object = PyTuple_GetItem(args, 0); + PyObject *item1 = PyTuple_GetItem(args, 1); + if (PyUnicode_Check(item1)) + { + const char *name = PyUnicode_AsUTF8(item1); + PyObject *item2 = PyTuple_GetItem(args, 2); + if(PyTuple_Check(item2)) + { + ret = PyUNO_invoke(object, name, item2); + } + else + { + OString buf = OString::Concat("uno.invoke expects a tuple as 3rd argument, got ") + + PyUnicode_AsUTF8(PyObject_Str(item2)); + PyErr_SetString( + PyExc_RuntimeError, buf.getStr()); + } + } + else + { + OString buf = OString::Concat("uno.invoke expected a string as 2nd argument, got ") + + PyUnicode_AsUTF8(PyObject_Str(item1)); + PyErr_SetString( + PyExc_RuntimeError, buf.getStr()); + } + } + else + { + OString buf = "uno.invoke expects object, name, (arg1, arg2, ... )\n"_ostr; + PyErr_SetString(PyExc_RuntimeError, buf.getStr()); + } + return ret; +} + +static PyObject *getCurrentContext( + SAL_UNUSED_PARAMETER PyObject *, SAL_UNUSED_PARAMETER PyObject * ) +{ + PyRef ret; + try + { + Runtime runtime; + ret = runtime.any2PyObject( + Any( css::uno::getCurrentContext() ) ); + } + catch( const css::uno::Exception & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + return ret.getAcquired(); +} + +static PyObject *setCurrentContext( + SAL_UNUSED_PARAMETER PyObject *, SAL_UNUSED_PARAMETER PyObject * args ) +{ + PyRef ret; + try + { + if( PyTuple_Check( args ) && PyTuple_Size( args ) == 1 ) + { + + Runtime runtime; + Any a = runtime.pyObject2Any( PyTuple_GetItem( args, 0 ) ); + + Reference< css::uno::XCurrentContext > context; + + if( (a.hasValue() && (a >>= context)) || ! a.hasValue() ) + { + ret = css::uno::setCurrentContext( context ) ? Py_True : Py_False; + } + else + { + OString buf = + OString::Concat("uno.setCurrentContext expects an XComponentContext implementation, got ") + + PyUnicode_AsUTF8(PyObject_Str(PyTuple_GetItem(args, 0))); + PyErr_SetString( + PyExc_RuntimeError, buf.getStr() ); + } + } + else + { + OString buf = "uno.setCurrentContext expects exactly one argument (the current Context)\n"_ostr; + PyErr_SetString( + PyExc_RuntimeError, buf.getStr() ); + } + } + catch( const css::uno::Exception & e ) + { + raisePyExceptionWithAny( Any( e ) ); + } + return ret.getAcquired(); +} + +static PyObject *sal_debug( + SAL_UNUSED_PARAMETER PyObject *, SAL_UNUSED_PARAMETER PyObject * args ) +{ + Py_INCREF( Py_None ); + if( !PyTuple_Check( args ) || PyTuple_Size( args) != 1 ) + return Py_None; + + OUString line = pyString2ustring( PyTuple_GetItem( args, 0 ) ); + + SAL_DEBUG(line); + + return Py_None; +} + +} + +struct PyMethodDef PyUNOModule_methods [] = +{ + {"private_initTestEnvironment", initTestEnvironment, METH_VARARGS, nullptr}, + {"private_deinitTestEnvironment", deinitTestEnvironment, METH_VARARGS, nullptr}, + {"getComponentContext", getComponentContext, METH_VARARGS, nullptr}, + {"_createUnoStructHelper", reinterpret_cast<PyCFunction>(createUnoStructHelper), METH_VARARGS | METH_KEYWORDS, nullptr}, + {"getTypeByName", getTypeByName, METH_VARARGS, nullptr}, + {"getConstantByName", getConstantByName, METH_VARARGS, nullptr}, + {"getClass", getClass, METH_VARARGS, nullptr}, + {"checkEnum", checkEnum, METH_VARARGS, nullptr}, + {"checkType", checkType, METH_VARARGS, nullptr}, + {"generateUuid", generateUuid, METH_VARARGS, nullptr}, + {"systemPathToFileUrl", systemPathToFileUrl, METH_VARARGS, nullptr}, + {"fileUrlToSystemPath", fileUrlToSystemPath, METH_VARARGS, nullptr}, + {"absolutize", absolutize, METH_VARARGS | METH_KEYWORDS, nullptr}, + {"isInterface", isInterface, METH_VARARGS, nullptr}, + {"invoke", invoke, METH_VARARGS | METH_KEYWORDS, nullptr}, + {"setCurrentContext", setCurrentContext, METH_VARARGS, nullptr}, + {"getCurrentContext", getCurrentContext, METH_VARARGS, nullptr}, + {"sal_debug", sal_debug, METH_VARARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +} + +extern "C" +PyObject* PyInit_pyuno() +{ + PyUNO_initType(); + PyUNOStruct_initType(); + // noop when called already, otherwise needed to allow multiple threads +#if PY_VERSION_HEX < 0x03090000 + PyEval_InitThreads(); +#endif + static struct PyModuleDef moduledef = + { + PyModuleDef_HEAD_INIT, + "pyuno", // module name + nullptr, // module documentation + -1, // module keeps state in global variables, + PyUNOModule_methods, // modules methods + nullptr, // m_reload (must be 0) + nullptr, // m_traverse + nullptr, // m_clear + nullptr, // m_free + }; + return PyModule_Create(&moduledef); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_runtime.cxx b/pyuno/source/module/pyuno_runtime.cxx new file mode 100644 index 0000000000..cb95e5a46d --- /dev/null +++ b/pyuno/source/module/pyuno_runtime.cxx @@ -0,0 +1,1012 @@ +/* -*- Mode: C++; eval:(c-set-style "bsd"); tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <sal/log.hxx> +#include <config_folders.h> + +#include "pyuno_impl.hxx" + +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <osl/module.h> +#include <osl/process.h> +#include <rtl/ustrbuf.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ref.hxx> + +#include <typelib/typedescription.hxx> + +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/beans/XMaterialHolder.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/script/InvocationAdapterFactory.hpp> +#include <com/sun/star/script/XInvocation2.hpp> +#include <com/sun/star/reflection/theCoreReflection.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> + +#include <vector> + +using com::sun::star::uno::Reference; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Any; +using com::sun::star::uno::TypeDescription; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Type; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::Exception; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::XComponentContext; +using com::sun::star::lang::WrappedTargetRuntimeException; +using com::sun::star::lang::XSingleServiceFactory; +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::reflection::theCoreReflection; +using com::sun::star::reflection::InvocationTargetException; +using com::sun::star::script::Converter; +using com::sun::star::script::XTypeConverter; +using com::sun::star::script::XInvocation; +using com::sun::star::beans::XMaterialHolder; +using com::sun::star::beans::theIntrospection; + +namespace pyuno +{ + +static PyTypeObject RuntimeImpl_Type = +{ + PyVarObject_HEAD_INIT (&PyType_Type, 0) + "pyuno_runtime", + sizeof (RuntimeImpl), + 0, + RuntimeImpl::del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + , 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +/*---------------------------------------------------------------------- + Runtime implementation + -----------------------------------------------------------------------*/ +/// @throws css::uno::RuntimeException +static void getRuntimeImpl( PyRef & globalDict, PyRef &runtimeImpl ) +{ + PyThreadState * state = PyThreadState_Get(); + if( ! state ) + { + throw RuntimeException( "python global interpreter must be held (thread must be attached)" ); + } + + PyObject* pModule = PyImport_AddModule("__main__"); + + if (!pModule) + { + throw RuntimeException("can't import __main__ module"); + } + + globalDict = PyRef( PyModule_GetDict(pModule)); + + if( ! globalDict.is() ) // FATAL ! + { + throw RuntimeException("can't find __main__ module"); + } + runtimeImpl = PyDict_GetItemString( globalDict.get() , "pyuno_runtime" ); +} + +/// @throws RuntimeException +static PyRef importUnoModule( ) +{ + // import the uno module + PyRef module( PyImport_ImportModule( "uno" ), SAL_NO_ACQUIRE, NOT_NULL ); + if( PyErr_Occurred() ) + { + PyRef excType, excValue, excTraceback; + PyErr_Fetch( reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback)); + // As of Python 2.7 this gives a rather non-useful "<traceback object at 0xADDRESS>", + // but it is the best we can do in the absence of uno._uno_extract_printable_stacktrace + // Who knows, a future Python might print something better. + PyRef str( PyObject_Str( excTraceback.get() ), SAL_NO_ACQUIRE ); + + OUStringBuffer buf; + buf.append( "python object raised an unknown exception (" ); + PyRef valueRep( PyObject_Repr( excValue.get() ), SAL_NO_ACQUIRE ); + buf.appendAscii( PyUnicode_AsUTF8( valueRep.get())).append( ", traceback follows\n" ); + buf.appendAscii( PyUnicode_AsUTF8( str.get() ) ); + buf.append( ")" ); + throw RuntimeException( buf.makeStringAndClear() ); + } + PyRef dict( PyModule_GetDict( module.get() ) ); + return dict; +} + +static void readLoggingConfig( sal_Int32 *pLevel, FILE **ppFile ) +{ + *pLevel = LogLevel::NONE; + *ppFile = nullptr; + OUString fileName; + osl_getModuleURLFromFunctionAddress( + reinterpret_cast< oslGenericFunction >(readLoggingConfig), + &fileName.pData ); + fileName = fileName.copy( fileName.lastIndexOf( '/' )+1 ); +#ifdef MACOSX + fileName += "../" LIBO_ETC_FOLDER "/"; +#endif + fileName += SAL_CONFIGFILE("pyuno" ); + rtl::Bootstrap bootstrapHandle( fileName ); + + OUString str; + if( bootstrapHandle.getFrom( "PYUNO_LOGLEVEL", str ) ) + { + if ( str == "NONE" ) + *pLevel = LogLevel::NONE; + else if ( str == "CALL" ) + *pLevel = LogLevel::CALL; + else if ( str == "ARGS" ) + *pLevel = LogLevel::ARGS; + else + { + fprintf( stderr, "unknown loglevel %s\n", + OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + } + if( *pLevel <= LogLevel::NONE ) + return; + + *ppFile = stdout; + if( !bootstrapHandle.getFrom( "PYUNO_LOGTARGET", str ) ) + return; + + if ( str == "stdout" ) + *ppFile = stdout; + else if ( str == "stderr" ) + *ppFile = stderr; + else + { + osl_getSystemPathFromFileURL( str.pData, &str.pData); + OString o = OUStringToOString( str, osl_getThreadTextEncoding() ); + + oslProcessInfo data; + data.Size = sizeof( data ); + if (osl_getProcessInfo( + nullptr , osl_Process_IDENTIFIER , &data ) == osl_Process_E_None) + { + o += "."; + o += OString::number(data.Ident); + } + + *ppFile = fopen( o.getStr() , "w" ); + if ( *ppFile ) + { + // do not buffer (useful if e.g. analyzing a crash) + setvbuf( *ppFile, nullptr, _IONBF, 0 ); + } + else + { + fprintf( stderr, "couldn't create file %s\n", + OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() ); + + } + } +} + +/*------------------------------------------------------------------- + RuntimeImpl implementations + *-------------------------------------------------------------------*/ +PyRef stRuntimeImpl::create( const Reference< XComponentContext > &ctx ) +{ + RuntimeImpl *me = PyObject_New (RuntimeImpl, &RuntimeImpl_Type); + if( ! me ) + throw RuntimeException( "cannot instantiate pyuno::RuntimeImpl" ); + me->cargo = nullptr; + // must use a different struct here, as the PyObject_New + // makes C++ unusable + RuntimeCargo *c = new RuntimeCargo; + readLoggingConfig( &(c->logLevel) , &(c->logFile) ); + log( c, LogLevel::CALL, "Instantiating pyuno bridge" ); + + c->valid = true; + c->xContext = ctx; + c->xInvocation = Reference< XSingleServiceFactory > ( + ctx->getServiceManager()->createInstanceWithContext( + "com.sun.star.script.Invocation", + ctx ), + css::uno::UNO_QUERY_THROW ); + + c->xTypeConverter = Converter::create(ctx); + if( ! c->xTypeConverter.is() ) + throw RuntimeException( "pyuno: couldn't instantiate typeconverter service" ); + + c->xCoreReflection = theCoreReflection::get(ctx); + + c->xAdapterFactory = css::script::InvocationAdapterFactory::create(ctx); + + c->xIntrospection = theIntrospection::get(ctx); + + Any a = ctx->getValueByName("/singletons/com.sun.star.reflection.theTypeDescriptionManager"); + a >>= c->xTdMgr; + if( ! c->xTdMgr.is() ) + throw RuntimeException( "pyuno: couldn't retrieve typedescriptionmanager" ); + + me->cargo =c; + return PyRef( reinterpret_cast< PyObject * > ( me ), SAL_NO_ACQUIRE ); +} + +void stRuntimeImpl::del(PyObject* self) +{ + RuntimeImpl *me = reinterpret_cast< RuntimeImpl * > ( self ); + if( me->cargo->logFile ) + fclose( me->cargo->logFile ); + delete me->cargo; + PyObject_Del (self); +} + + +void Runtime::initialize( const Reference< XComponentContext > & ctx ) +{ + PyRef globalDict, runtime; + getRuntimeImpl( globalDict , runtime ); + RuntimeImpl *impl = reinterpret_cast< RuntimeImpl * > (runtime.get()); + + if( runtime.is() && impl->cargo->valid ) + { + throw RuntimeException("pyuno runtime has already been initialized before" ); + } + PyRef keep( RuntimeImpl::create( ctx ) ); + PyDict_SetItemString( globalDict.get(), "pyuno_runtime" , keep.get() ); + Py_XINCREF( keep.get() ); +} + + +bool Runtime::isInitialized() +{ + PyRef globalDict, runtime; + getRuntimeImpl( globalDict , runtime ); + RuntimeImpl *impl = reinterpret_cast< RuntimeImpl * > (runtime.get()); + return runtime.is() && impl->cargo->valid; +} + +Runtime::Runtime() + : impl( nullptr ) +{ + PyRef globalDict, runtime; + getRuntimeImpl( globalDict , runtime ); + if( ! runtime.is() ) + { + throw RuntimeException( + "pyuno runtime is not initialized, " + "(the pyuno.bootstrap needs to be called before using any uno classes)" ); + } + impl = reinterpret_cast< RuntimeImpl * > (runtime.get()); + Py_XINCREF( runtime.get() ); +} + +Runtime::Runtime( const Runtime & r ) +{ + impl = r.impl; + Py_XINCREF( reinterpret_cast< PyObject * >(impl) ); +} + +Runtime::~Runtime() +{ + Py_XDECREF( reinterpret_cast< PyObject * >(impl) ); +} + +Runtime & Runtime::operator = ( const Runtime & r ) +{ + PyRef temp( reinterpret_cast< PyObject * >(r.impl) ); + Py_XINCREF( temp.get() ); + Py_XDECREF( reinterpret_cast< PyObject * >(impl) ); + impl = r.impl; + return *this; +} + +PyRef Runtime::any2PyObject (const Any &a ) const +{ + if( ! impl->cargo->valid ) + { + throw RuntimeException("pyuno runtime must be initialized before calling any2PyObject" ); + } + + switch (a.getValueTypeClass ()) + { + case css::uno::TypeClass_VOID: + { + Py_INCREF (Py_None); + return PyRef(Py_None); + } + case css::uno::TypeClass_CHAR: + { + sal_Unicode c = *o3tl::forceAccess<sal_Unicode>(a); + return PyRef( PyUNO_char_new( c , *this ), SAL_NO_ACQUIRE ); + } + case css::uno::TypeClass_BOOLEAN: + { + bool b; + if ((a >>= b) && b) + return Py_True; + else + return Py_False; + } + case css::uno::TypeClass_BYTE: + case css::uno::TypeClass_SHORT: + case css::uno::TypeClass_UNSIGNED_SHORT: + case css::uno::TypeClass_LONG: + { + sal_Int32 l = 0; + a >>= l; + return PyRef( PyLong_FromLong (l), SAL_NO_ACQUIRE ); + } + case css::uno::TypeClass_UNSIGNED_LONG: + { + sal_uInt32 l = 0; + a >>= l; + return PyRef( PyLong_FromUnsignedLong (l), SAL_NO_ACQUIRE ); + } + case css::uno::TypeClass_HYPER: + { + sal_Int64 l = 0; + a >>= l; + return PyRef( PyLong_FromLongLong (l), SAL_NO_ACQUIRE); + } + case css::uno::TypeClass_UNSIGNED_HYPER: + { + sal_uInt64 l = 0; + a >>= l; + return PyRef( PyLong_FromUnsignedLongLong (l), SAL_NO_ACQUIRE); + } + case css::uno::TypeClass_FLOAT: + { + float f = 0.0; + a >>= f; + return PyRef(PyFloat_FromDouble (f), SAL_NO_ACQUIRE); + } + case css::uno::TypeClass_DOUBLE: + { + double d = 0.0; + a >>= d; + return PyRef( PyFloat_FromDouble (d), SAL_NO_ACQUIRE); + } + case css::uno::TypeClass_STRING: + { + OUString tmp_ostr; + a >>= tmp_ostr; + return ustring2PyUnicode( tmp_ostr ); + } + case css::uno::TypeClass_TYPE: + { + Type t; + a >>= t; + OString o = OUStringToOString( t.getTypeName(), RTL_TEXTENCODING_ASCII_US ); + return PyRef( + PyUNO_Type_new ( + o.getStr(), t.getTypeClass(), *this), + SAL_NO_ACQUIRE); + } + case css::uno::TypeClass_ANY: + { + //I don't think this can happen. + Py_INCREF (Py_None); + return Py_None; + } + case css::uno::TypeClass_ENUM: + { + sal_Int32 l = *static_cast<sal_Int32 const *>(a.getValue()); + TypeDescription desc( a.getValueType() ); + if( desc.is() ) + { + desc.makeComplete(); + typelib_EnumTypeDescription *pEnumDesc = + reinterpret_cast<typelib_EnumTypeDescription *>(desc.get()); + for( int i = 0 ; i < pEnumDesc->nEnumValues ; i ++ ) + { + if( pEnumDesc->pEnumValues[i] == l ) + { + OString v = OUStringToOString( OUString::unacquired(&pEnumDesc->ppEnumNames[i]), RTL_TEXTENCODING_ASCII_US); + OString e = OUStringToOString( OUString::unacquired(&pEnumDesc->aBase.pTypeName), RTL_TEXTENCODING_ASCII_US); + return PyRef( PyUNO_Enum_new(e.getStr(),v.getStr(), *this ), SAL_NO_ACQUIRE ); + } + } + } + throw RuntimeException( "Any carries enum " + a.getValueType().getTypeName() + + " with invalid value " + OUString::number(l) ); + } + case css::uno::TypeClass_EXCEPTION: + case css::uno::TypeClass_STRUCT: + { + PyRef excClass = getClass( a.getValueType().getTypeName(), *this ); + PyRef value = PyUNOStruct_new( a, getImpl()->cargo->xInvocation ); + PyRef argsTuple( PyTuple_New( 1 ) , SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem( argsTuple.get() , 0 , value.getAcquired() ); + PyRef ret( PyObject_CallObject( excClass.get() , argsTuple.get() ), SAL_NO_ACQUIRE ); + if( ! ret.is() ) + { + throw RuntimeException( "Couldn't instantiate python representation of structured UNO type " + + a.getValueType().getTypeName() ); + } + + if( auto e = o3tl::tryAccess<css::uno::Exception>(a) ) + { + // add the message in a standard python way ! + PyRef args( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL ); + + PyRef pymsg = ustring2PyString( e->Message ); + PyTuple_SetItem( args.get(), 0 , pymsg.getAcquired() ); + // the exception base functions want to have an "args" tuple, + // which contains the message + PyObject_SetAttrString( ret.get(), "args", args.get() ); + } + return ret; + } + case css::uno::TypeClass_SEQUENCE: + { + Sequence<Any> s; + + Sequence< sal_Int8 > byteSequence; + if( a >>= byteSequence ) + { + // byte sequence is treated in a special way because of performance reasons + // @since 0.9.2 + return PyRef( PyUNO_ByteSequence_new( byteSequence, *this ), SAL_NO_ACQUIRE ); + } + else + { + Reference< XTypeConverter > tc = getImpl()->cargo->xTypeConverter; + tc->convertTo (a, cppu::UnoType<decltype(s)>::get()) >>= s; + PyRef tuple( PyTuple_New (s.getLength()), SAL_NO_ACQUIRE, NOT_NULL); + int i=0; + try + { + for ( i = 0; i < s.getLength (); i++) + { + PyRef element = any2PyObject (tc->convertTo (s[i], s[i].getValueType() )); + OSL_ASSERT( element.is() ); + PyTuple_SetItem( tuple.get(), i, element.getAcquired() ); + } + } + catch( css::uno::Exception & ) + { + for( ; i < s.getLength() ; i ++ ) + { + Py_INCREF( Py_None ); + PyTuple_SetItem( tuple.get(), i, Py_None ); + } + throw; + } + return tuple; + } + } + case css::uno::TypeClass_INTERFACE: + { + Reference<XInterface> tmp_interface; + a >>= tmp_interface; + if (!tmp_interface.is ()) + return Py_None; + + return PyUNO_new( a, getImpl()->cargo->xInvocation ); + } + default: + { + throw RuntimeException( "Unknown UNO type class " + OUString::number(static_cast<int>(a.getValueTypeClass())) ); + } + } +} + +static Sequence< Type > invokeGetTypes( const Runtime & r , PyObject * o ) +{ + Sequence< Type > ret; + + PyRef method( PyObject_GetAttrString( o , "getTypes" ), SAL_NO_ACQUIRE ); + raiseInvocationTargetExceptionWhenNeeded( r ); + if( method.is() && PyCallable_Check( method.get() ) ) + { + PyRef types( PyObject_CallObject( method.get(), nullptr ) , SAL_NO_ACQUIRE ); + raiseInvocationTargetExceptionWhenNeeded( r ); + if( types.is() && PyTuple_Check( types.get() ) ) + { + int size = PyTuple_Size( types.get() ); + + // add the XUnoTunnel interface for uno object identity concept (hack) + ret.realloc( size + 1 ); + auto pret = ret.getArray(); + for( int i = 0 ; i < size ; i ++ ) + { + Any a = r.pyObject2Any(PyTuple_GetItem(types.get(),i)); + a >>= pret[i]; + } + pret[size] = cppu::UnoType<css::lang::XUnoTunnel>::get(); + } + } + return ret; +} + +static OUString +lcl_ExceptionMessage(PyObject *const o, OUString const*const pWrapped) +{ + OUStringBuffer buf; + buf.append("Couldn't convert "); + PyRef reprString( PyObject_Str(o), SAL_NO_ACQUIRE ); + buf.appendAscii( PyUnicode_AsUTF8(reprString.get()) ); + buf.append(" to a UNO type"); + if (pWrapped) + { + buf.append("; caught exception: "); + buf.append(*pWrapped); + } + return buf.makeStringAndClear(); +} + +// For Python 2.7 - see https://bugs.python.org/issue24161 +// Fills aSeq and returns true if pObj is a valid iterator +bool Runtime::pyIterUnpack( PyObject *const pObj, Any &a ) const +{ + if( !PyIter_Check( pObj )) + return false; + + PyObject *pItem = PyIter_Next( pObj ); + if( !pItem ) + { + if( PyErr_Occurred() ) + { + PyErr_Clear(); + return false; + } + return true; + } + + ::std::vector<Any> items; + do + { + PyRef rItem( pItem, SAL_NO_ACQUIRE ); + items.push_back( pyObject2Any( rItem.get() ) ); + pItem = PyIter_Next( pObj ); + } + while( pItem ); + a <<= comphelper::containerToSequence(items); + return true; +} + +Any Runtime::pyObject2Any(const PyRef & source, enum ConversionMode mode) const +{ + if (!impl || !impl->cargo->valid) + { + throw RuntimeException("pyuno runtime must be initialized before calling any2PyObject" ); + } + + Any a; + PyObject *o = source.get(); + if( Py_None == o ) + { + + } + else if (PyLong_Check (o)) + { + // Convert the Python 3 booleans that are actually of type PyLong. + if(o == Py_True) + { + a <<= true; + } + else if(o == Py_False) + { + a <<= false; + } + else + { + sal_Int64 l = static_cast<sal_Int64>(PyLong_AsLong (o)); + if( l < 128 && l >= -128 ) + { + sal_Int8 b = static_cast<sal_Int8>(l); + a <<= b; + } + else if( l <= 0x7fff && l >= -0x8000 ) + { + sal_Int16 s = static_cast<sal_Int16>(l); + a <<= s; + } + else if( l <= SAL_CONST_INT64(0x7fffffff) && + l >= -SAL_CONST_INT64(0x80000000) ) + { + sal_Int32 l32 = static_cast<sal_Int32>(l); + a <<= l32; + } + else + { + a <<= l; + } + } + } + else if (PyFloat_Check (o)) + { + double d = PyFloat_AsDouble (o); + a <<= d; + } + else if (PyBytes_Check(o) || PyUnicode_Check(o)) + { + a <<= pyString2ustring(o); + } + else if (PyTuple_Check (o)) + { + Sequence<Any> s (PyTuple_Size (o)); + auto sRange = asNonConstRange(s); + for (Py_ssize_t i = 0; i < PyTuple_Size (o); i++) + { + sRange[i] = pyObject2Any (PyTuple_GetItem (o, i), mode ); + } + a <<= s; + } + else if (PyList_Check (o)) + { + Py_ssize_t l = PyList_Size (o); + Sequence<Any> s (l); + auto sRange = asNonConstRange(s); + for (Py_ssize_t i = 0; i < l; i++) + { + sRange[i] = pyObject2Any (PyList_GetItem (o, i), mode ); + } + a <<= s; + } + else if (!pyIterUnpack (o, a)) + { + Runtime runtime; + // should be removed, in case ByteSequence gets derived from String + if( PyObject_IsInstance( o, getByteSequenceClass( runtime ).get() ) ) + { + PyRef str(PyObject_GetAttrString( o , "value" ),SAL_NO_ACQUIRE); + Sequence< sal_Int8 > seq; + if( PyBytes_Check( str.get() ) ) + { + seq = Sequence<sal_Int8 > ( + reinterpret_cast<sal_Int8*>(PyBytes_AsString(str.get())), PyBytes_Size(str.get())); + } + a <<= seq; + } + else + if( PyObject_IsInstance( o, getTypeClass( runtime ).get() ) ) + { + Type t = PyType2Type( o ); + a <<= t; + } + else if( PyObject_IsInstance( o, getEnumClass( runtime ).get() ) ) + { + a = PyEnum2Enum( o ); + } + else if( isInstanceOfStructOrException( o ) ) + { + PyRef struc(PyObject_GetAttrString( o , "value" ),SAL_NO_ACQUIRE); + PyUNO * obj = reinterpret_cast<PyUNO*>(struc.get()); + Reference< XMaterialHolder > holder( obj->members->xInvocation, UNO_QUERY ); + if( !holder.is( ) ) + { + throw RuntimeException( + "struct or exception wrapper does not support XMaterialHolder" ); + } + + a = holder->getMaterial(); + + } + else if( PyObject_IsInstance( o, getPyUnoClass().get() ) ) + { + PyUNO* o_pi = reinterpret_cast<PyUNO*>(o); + a = o_pi->members->wrappedObject; + } + else if( PyObject_IsInstance( o, getPyUnoStructClass().get() ) ) + { + PyUNO* o_pi = reinterpret_cast<PyUNO*>(o); + Reference<XMaterialHolder> my_mh (o_pi->members->xInvocation, css::uno::UNO_QUERY_THROW); + a = my_mh->getMaterial(); + } + else if( PyObject_IsInstance( o, getCharClass( runtime ).get() ) ) + { + a <<= PyChar2Unicode( o ); + } + else if( PyObject_IsInstance( o, getAnyClass( runtime ).get() ) ) + { + if( ACCEPT_UNO_ANY != mode ) + { + throw RuntimeException( + "uno.Any instance not accepted during method call, " + "use uno.invoke instead" ); + } + + a = pyObject2Any( PyRef( PyObject_GetAttrString( o , "value" ), SAL_NO_ACQUIRE) ); + Type t; + pyObject2Any( PyRef( PyObject_GetAttrString( o, "type" ), SAL_NO_ACQUIRE ) ) >>= t; + + try + { + a = getImpl()->cargo->xTypeConverter->convertTo( a, t ); + } + catch( const css::uno::Exception & e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetRuntimeException( + e.Message, e.Context, anyEx); + } + + } + else + { + Reference< XInterface > mappedObject; + Reference< XInvocation > adapterObject; + + // instance already mapped out to the world ? + PyRef2Adapter::iterator ii = impl->cargo->mappedObjects.find( PyRef( o ) ); + if( ii != impl->cargo->mappedObjects.end() ) + { + adapterObject = ii->second; + } + + if( adapterObject.is() ) + { + // object got already bridged ! + auto pAdapter = comphelper::getFromUnoTunnel<Adapter>(adapterObject); + + mappedObject = impl->cargo->xAdapterFactory->createAdapter( + adapterObject, pAdapter->getWrappedTypes() ); + } + else + { + try { + Sequence<Type> interfaces = invokeGetTypes(*this, o); + if (interfaces.getLength()) + { + rtl::Reference<Adapter> pAdapter = new Adapter( o, interfaces ); + mappedObject = + getImpl()->cargo->xAdapterFactory->createAdapter( + pAdapter, interfaces ); + + // keep a list of exported objects to ensure object identity ! + impl->cargo->mappedObjects[ PyRef(o) ] = + css::uno::WeakReference< XInvocation > ( pAdapter ); + } + } catch (InvocationTargetException const& e) { + OUString const msg(lcl_ExceptionMessage(o, &e.Message)); + throw WrappedTargetRuntimeException( // re-wrap that + msg, e.Context, e.TargetException); + } + } + if( mappedObject.is() ) + { + a <<= mappedObject; + } + else + { + OUString const msg(lcl_ExceptionMessage(o, nullptr)); + throw RuntimeException(msg); + } + } + } + return a; +} + +Any Runtime::extractUnoException( const PyRef & excType, const PyRef &excValue, const PyRef &excTraceback) const +{ + OUString str; + Any ret; + if( excTraceback.is() ) + { + Exception e; + PyRef unoModule; + if ( impl ) + { + try + { + unoModule = impl->cargo->getUnoModule(); + } + catch (const Exception &ei) + { + e=ei; + } + } + if( unoModule.is() ) + { + PyRef extractTraceback( + PyDict_GetItemString(unoModule.get(),"_uno_extract_printable_stacktrace" ) ); + + if( PyCallable_Check(extractTraceback.get()) ) + { + PyRef args( PyTuple_New( 1), SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem( args.get(), 0, excTraceback.getAcquired() ); + PyRef pyStr( PyObject_CallObject( extractTraceback.get(),args.get() ), SAL_NO_ACQUIRE); + str = OUString::fromUtf8(PyUnicode_AsUTF8(pyStr.get())); + } + else + { + str = "Couldn't find uno._uno_extract_printable_stacktrace"; + } + } + else + { + str = "Could not load uno.py, no stacktrace available"; + if ( !e.Message.isEmpty() ) + { + str += " (Error loading uno.py: " + e.Message + ")"; + } + } + + } + else + { + // it may occur, that no traceback is given (e.g. only native code below) + str = "no traceback available"; + } + + if( isInstanceOfStructOrException( excValue.get() ) ) + { + ret = pyObject2Any( excValue ); + } + else + { + OUStringBuffer buf; + PyRef typeName( PyObject_Str( excType.get() ), SAL_NO_ACQUIRE ); + if( typeName.is() ) + { + buf.appendAscii( PyUnicode_AsUTF8( typeName.get() ) ); + } + else + { + buf.append( "no typename available" ); + } + buf.append( ": " ); + PyRef valueRep( PyObject_Str( excValue.get() ), SAL_NO_ACQUIRE ); + if( valueRep.is() ) + { + buf.appendAscii( PyUnicode_AsUTF8( valueRep.get())); + } + else + { + buf.append( "Couldn't convert exception value to a string" ); + } + buf.append( ", traceback follows\n" ); + if( !str.isEmpty() ) + { + buf.append( str ); + buf.append( "\n" ); + } + else + { + buf.append( ", no traceback available\n" ); + } + RuntimeException e(buf.makeStringAndClear()); + SAL_WARN("pyuno.runtime", "Python exception: " << e.Message); + ret <<= e; + } + return ret; +} + + +PyThreadAttach::PyThreadAttach( PyInterpreterState *interp) + : m_isNewState(false) +{ + // note: *may* be called recursively, with PyThreadDetach between - in + // that case, don't create *new* PyThreadState but reuse! + tstate = PyGILState_GetThisThreadState(); // from TLS, possibly detached + if (!tstate) + { + m_isNewState = true; + tstate = PyThreadState_New( interp ); + } + if( !tstate ) + throw RuntimeException( "Couldn't create a pythreadstate" ); + PyEval_AcquireThread( tstate); +} + +PyThreadAttach::~PyThreadAttach() +{ + if (m_isNewState) + { // Clear needs GIL! + PyThreadState_Clear( tstate ); + // note: PyThreadState_Delete(tstate) cannot be called, it will assert + // because it requires a PyThreadState to be set, but not the tstate! + PyThreadState_DeleteCurrent(); + } + else + { + PyEval_ReleaseThread( tstate ); + } +} + +PyThreadDetach::PyThreadDetach() +{ + tstate = PyThreadState_Get(); + PyEval_ReleaseThread( tstate ); + // tstate must not be deleted here! lots of pointers to it on the stack +} + + /** Acquires the global interpreter lock again + + */ +PyThreadDetach::~PyThreadDetach() +{ + PyEval_AcquireThread( tstate ); +} + + +PyRef const & RuntimeCargo::getUnoModule() +{ + if( ! dictUnoModule.is() ) + { + dictUnoModule = importUnoModule(); + } + return dictUnoModule; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_struct.cxx b/pyuno/source/module/pyuno_struct.cxx new file mode 100644 index 0000000000..c8fd7e9879 --- /dev/null +++ b/pyuno/source/module/pyuno_struct.cxx @@ -0,0 +1,397 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <sal/config.h> + +#include <com/sun/star/beans/UnknownPropertyException.hpp> +#include <com/sun/star/beans/XMaterialHolder.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/XInvocation2.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include "pyuno_impl.hxx" + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Reference; +using com::sun::star::uno::Any; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::TypeClass; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Exception; +using com::sun::star::lang::XSingleServiceFactory; +using com::sun::star::script::XInvocation2; +using com::sun::star::beans::XMaterialHolder; + +namespace pyuno +{ + +static void PyUNOStruct_del( PyObject* self ) +{ + PyUNO *me = reinterpret_cast<PyUNO*>( self ); + { + PyThreadDetach antiguard; + delete me->members; + } + PyObject_Del( self ); +} + +static PyObject *PyUNOStruct_str( PyObject *self ) +{ + PyUNO *me = reinterpret_cast<PyUNO*>( self ); + OString buf; + + Reference<XMaterialHolder> rHolder( me->members->xInvocation,UNO_QUERY ); + if( rHolder.is() ) + { + PyThreadDetach antiguard; + Any a = rHolder->getMaterial(); + OUString s = val2str( a.getValue(), a.getValueType().getTypeLibType() ); + buf = OUStringToOString( s, RTL_TEXTENCODING_ASCII_US ); + } + + return PyUnicode_FromString( buf.getStr()); +} + +static PyObject *PyUNOStruct_repr( PyObject *self ) +{ + PyUNO *me = reinterpret_cast<PyUNO*>( self ); + PyObject *ret = nullptr; + + if( me->members->wrappedObject.getValueType().getTypeClass() + == css::uno::TypeClass_EXCEPTION ) + { + Reference< XMaterialHolder > rHolder(me->members->xInvocation,UNO_QUERY); + if( rHolder.is() ) + { + Any a = rHolder->getMaterial(); + Exception e; + a >>= e; + ret = ustring2PyUnicode(e.Message ).getAcquired(); + } + } + else + { + ret = PyUNOStruct_str( self ); + } + + return ret; +} + +static PyObject* PyUNOStruct_dir( PyObject *self ) +{ + PyUNO *me = reinterpret_cast<PyUNO*>( self ); + + PyObject* member_list = nullptr; + + try + { + member_list = PyList_New( 0 ); + const css::uno::Sequence<OUString> aMemberNames = me->members->xInvocation->getMemberNames(); + for( const auto& aMember : aMemberNames ) + { + // setitem steals a reference + PyList_Append( member_list, ustring2PyString( aMember ).getAcquired() ); + } + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + + return member_list; +} + +static PyObject* PyUNOStruct_getattr( PyObject* self, char* name ) +{ + PyUNO *me = reinterpret_cast<PyUNO*>( self ); + + try + { + Runtime runtime; + + me = reinterpret_cast<PyUNO*>(self); + if (strcmp (name, "__dict__") == 0) + { + Py_INCREF (Py_TYPE(me)->tp_dict); + return Py_TYPE(me)->tp_dict; + } + if( strcmp( name, "__class__" ) == 0 ) + { + return getClass( + me->members->wrappedObject.getValueType().getTypeName(), runtime ).getAcquired(); + } + + PyObject *pRet = PyObject_GenericGetAttr( self, PyUnicode_FromString( name ) ); + if( pRet ) + return pRet; + PyErr_Clear(); + + OUString attrName( OUString::createFromAscii( name ) ); + if( me->members->xInvocation->hasProperty( attrName ) ) + { + //Return the value of the property + Any anyRet; + { + PyThreadDetach antiguard; + anyRet = me->members->xInvocation->getValue( attrName ); + } + PyRef ret = runtime.any2PyObject( anyRet ); + Py_XINCREF( ret.get() ); + return ret.get(); + } + + //or else... + PyErr_SetString (PyExc_AttributeError, name); + } + catch( const css::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + } + catch( const css::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const css::lang::IllegalArgumentException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + catch( const RuntimeException &e ) + { + raisePyExceptionWithAny( Any(e) ); + } + + return nullptr; +} + +static int PyUNOStruct_setattr (PyObject* self, char* name, PyObject* value) +{ + PyUNO* me; + + me = reinterpret_cast<PyUNO*>(self); + try + { + Runtime runtime; + Any val= runtime.pyObject2Any(value, ACCEPT_UNO_ANY); + + OUString attrName( OUString::createFromAscii( name ) ); + { + PyThreadDetach antiguard; + if (me->members->xInvocation->hasProperty (attrName)) + { + me->members->xInvocation->setValue (attrName, val); + return 0; //Keep with Python's boolean system + } + } + } + catch( const css::reflection::InvocationTargetException & e ) + { + raisePyExceptionWithAny( e.TargetException ); + return 1; + } + catch( const css::beans::UnknownPropertyException & e ) + { + raisePyExceptionWithAny( Any(e) ); + return 1; + } + catch( const css::script::CannotConvertException &e ) + { + raisePyExceptionWithAny( Any(e) ); + return 1; + } + catch( const RuntimeException & e ) + { + raisePyExceptionWithAny( Any( e ) ); + return 1; + } + PyErr_SetString (PyExc_AttributeError, name); + return 1; //as above. +} + + +static PyObject* PyUNOStruct_cmp( PyObject *self, PyObject *that, int op ) +{ + PyObject *result; + + if(op != Py_EQ && op != Py_NE) + { + PyErr_SetString( PyExc_TypeError, "only '==' and '!=' comparisons are defined" ); + return nullptr; + } + if( self == that ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF( result ); + return result; + } + try + { + Runtime runtime; + if( PyObject_IsInstance( that, getPyUnoStructClass().get() ) ) + { + + PyUNO *me = reinterpret_cast< PyUNO * > ( self ); + PyUNO *other = reinterpret_cast< PyUNO * > ( that ); + css::uno::TypeClass tcMe = me->members->wrappedObject.getValueTypeClass(); + css::uno::TypeClass tcOther = other->members->wrappedObject.getValueTypeClass(); + + if( tcMe == tcOther ) + { + if( tcMe == css::uno::TypeClass_STRUCT || + tcMe == css::uno::TypeClass_EXCEPTION ) + { + Reference< XMaterialHolder > xMe( me->members->xInvocation,UNO_QUERY ); + Reference< XMaterialHolder > xOther( other->members->xInvocation,UNO_QUERY ); + if( xMe->getMaterial() == xOther->getMaterial() ) + { + result = (op == Py_EQ ? Py_True : Py_False); + Py_INCREF( result ); + return result; + } + } + } + } + } + catch( const css::uno::RuntimeException & e) + { + raisePyExceptionWithAny( Any( e ) ); + } + + result = (op == Py_EQ ? Py_False : Py_True); + Py_INCREF(result); + return result; +} + +static PyMethodDef PyUNOStructMethods[] = +{ + {"__dir__", reinterpret_cast<PyCFunction>(PyUNOStruct_dir), METH_NOARGS, nullptr}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyTypeObject PyUNOStructType = +{ + PyVarObject_HEAD_INIT( &PyType_Type, 0 ) + "pyuno.struct", + sizeof (PyUNO), + 0, + PyUNOStruct_del, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + PyUNOStruct_getattr, + PyUNOStruct_setattr, + /* this type does not exist in Python 3: (cmpfunc) */ nullptr, + PyUNOStruct_repr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + PyUNOStruct_str, + nullptr, + nullptr, + nullptr, + Py_TPFLAGS_HAVE_RICHCOMPARE, + nullptr, + nullptr, + nullptr, + PyUNOStruct_cmp, + 0, + nullptr, + nullptr, + PyUNOStructMethods, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr + , 0 +#if PY_VERSION_HEX >= 0x03040000 + , nullptr +#if PY_VERSION_HEX >= 0x03080000 + , nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#endif +#endif +}; + +int PyUNOStruct_initType() +{ + return PyType_Ready( &PyUNOStructType ); +} + +PyRef getPyUnoStructClass() +{ + return PyRef( reinterpret_cast< PyObject * > ( &PyUNOStructType ) ); +} + +PyRef PyUNOStruct_new ( + const Any &targetInterface, + const Reference<XSingleServiceFactory> &ssf ) +{ + Reference<XInvocation2> xInvocation; + + { + PyThreadDetach antiguard; + xInvocation.set( + ssf->createInstanceWithArguments( Sequence<Any>( &targetInterface, 1 ) ), css::uno::UNO_QUERY_THROW ); + } + if( !Py_IsInitialized() ) + throw RuntimeException(); + + PyUNO* self = PyObject_New (PyUNO, &PyUNOStructType); + if (self == nullptr) + return PyRef(); // == error + self->members = new PyUNOInternals; + self->members->xInvocation = xInvocation; + self->members->wrappedObject = targetInterface; + return PyRef( reinterpret_cast<PyObject*>(self), SAL_NO_ACQUIRE ); + +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_type.cxx b/pyuno/source/module/pyuno_type.cxx new file mode 100644 index 0000000000..a04b4e26d7 --- /dev/null +++ b/pyuno/source/module/pyuno_type.cxx @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ +#include "pyuno_impl.hxx" + +#include <o3tl/any.hxx> + +#include <typelib/typedescription.hxx> + + +using com::sun::star::uno::TypeClass; +using com::sun::star::uno::Type; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Any; +using com::sun::star::uno::TypeDescription; + +namespace pyuno +{ +const char *typeClassToString( TypeClass t ) +{ + const char * ret = nullptr; + switch (t) + { + case css::uno::TypeClass_VOID: + ret = "VOID"; break; + case css::uno::TypeClass_CHAR: + ret = "CHAR"; break; + case css::uno::TypeClass_BOOLEAN: + ret = "BOOLEAN"; break; + case css::uno::TypeClass_BYTE: + ret = "BYTE"; break; + case css::uno::TypeClass_SHORT: + ret = "SHORT"; break; + case css::uno::TypeClass_UNSIGNED_SHORT: + ret = "UNSIGNED_SHORT"; break; + case css::uno::TypeClass_LONG: + ret = "LONG"; break; + case css::uno::TypeClass_UNSIGNED_LONG: + ret = "UNSIGNED_LONG"; break; + case css::uno::TypeClass_HYPER: + ret = "HYPER"; break; + case css::uno::TypeClass_UNSIGNED_HYPER: + ret = "UNSIGNED_HYPER"; break; + case css::uno::TypeClass_FLOAT: + ret = "FLOAT"; break; + case css::uno::TypeClass_DOUBLE: + ret = "DOUBLE"; break; + case css::uno::TypeClass_STRING: + ret = "STRING"; break; + case css::uno::TypeClass_TYPE: + ret = "TYPE"; break; + case css::uno::TypeClass_ANY: + ret = "ANY";break; + case css::uno::TypeClass_ENUM: + ret = "ENUM";break; + case css::uno::TypeClass_STRUCT: + ret = "STRUCT"; break; + case css::uno::TypeClass_EXCEPTION: + ret = "EXCEPTION"; break; + case css::uno::TypeClass_SEQUENCE: + ret = "SEQUENCE"; break; + case css::uno::TypeClass_INTERFACE: + ret = "INTERFACE"; break; + case css::uno::TypeClass_TYPEDEF: + ret = "TYPEDEF"; break; + case css::uno::TypeClass_SERVICE: + ret = "SERVICE"; break; + case css::uno::TypeClass_MODULE: + ret = "MODULE"; break; + case css::uno::TypeClass_INTERFACE_METHOD: + ret = "INTERFACE_METHOD"; break; + case css::uno::TypeClass_INTERFACE_ATTRIBUTE: + ret = "INTERFACE_ATTRIBUTE"; break; + default: + ret = "UNKNOWN"; break; + } + return ret; +} + +static PyRef getClass( const Runtime & r , const char * name) +{ + return PyRef( PyDict_GetItemString( r.getImpl()->cargo->getUnoModule().get(), name ) ); +} + +PyRef getTypeClass( const Runtime & r ) +{ + return getClass( r , "Type" ); +} + +PyRef getEnumClass( const Runtime & r ) +{ + return getClass( r , "Enum" ); +} + +PyRef getCharClass( const Runtime & r ) +{ + return getClass( r , "Char" ); +} + +PyRef getByteSequenceClass( const Runtime & r ) +{ + return getClass( r , "ByteSequence" ); +} + +PyRef getAnyClass( const Runtime & r ) +{ + return getClass( r , "Any" ); +} + + +sal_Unicode PyChar2Unicode( PyObject *obj ) +{ + PyRef value( PyObject_GetAttrString( obj, "value" ), SAL_NO_ACQUIRE ); + if( ! PyUnicode_Check( value.get() ) ) + { + throw RuntimeException( + "attribute value of uno.Char is not a unicode string" ); + } + + if( PyUnicode_GetLength( value.get() ) < 1 ) + { + throw RuntimeException( + "uno.Char contains an empty unicode string"); + } + + sal_Unicode c = static_cast<sal_Unicode>(PyUnicode_ReadChar( value.get(), 0)); + return c; +} + +Any PyEnum2Enum( PyObject *obj ) +{ + Any ret; + PyRef typeName( PyObject_GetAttrString( obj,"typeName" ), SAL_NO_ACQUIRE); + PyRef value( PyObject_GetAttrString( obj, "value" ), SAL_NO_ACQUIRE); + if( !PyUnicode_Check( typeName.get() ) || ! PyUnicode_Check( value.get() ) ) + { + throw RuntimeException( + "attributes typeName and/or value of uno.Enum are not strings" ); + } + + OUString strTypeName( OUString::createFromAscii( PyUnicode_AsUTF8( typeName.get() ) ) ); + char const *stringValue = PyUnicode_AsUTF8( value.get() ); + + TypeDescription desc( strTypeName ); + if( !desc.is() ) + { + throw RuntimeException( "enum " + OUString::createFromAscii( PyUnicode_AsUTF8(typeName.get()) ) + " is unknown" ); + } + + if(desc.get()->eTypeClass != typelib_TypeClass_ENUM ) + { + throw RuntimeException( "pyuno.checkEnum: " + strTypeName + "is a " + + OUString::createFromAscii(typeClassToString( static_cast<css::uno::TypeClass>(desc.get()->eTypeClass))) + + ", expected ENUM" ); + } + + desc.makeComplete(); + + typelib_EnumTypeDescription *pEnumDesc = reinterpret_cast<typelib_EnumTypeDescription*>(desc.get()); + int i = 0; + for( i = 0; i < pEnumDesc->nEnumValues ; i ++ ) + { + if( OUString::unacquired(&pEnumDesc->ppEnumNames[i]).equalsAscii( stringValue ) ) + { + break; + } + } + if( i == pEnumDesc->nEnumValues ) + { + throw RuntimeException( "value " + OUString::createFromAscii( stringValue ) + + "is unknown in enum " + + OUString::createFromAscii( PyUnicode_AsUTF8( typeName.get() ) ) ); + } + ret = Any( &pEnumDesc->pEnumValues[i], desc.get()->pWeakRef ); + + return ret; +} + + +Type PyType2Type( PyObject * o ) +{ + PyRef pyName( PyObject_GetAttrString( o, "typeName" ), SAL_NO_ACQUIRE); + if( !PyUnicode_Check( pyName.get() ) ) + { + throw RuntimeException( + "type object does not have typeName property" ); + } + + PyRef pyTC( PyObject_GetAttrString( o, "typeClass" ), SAL_NO_ACQUIRE ); + Any enumValue = PyEnum2Enum( pyTC.get() ); + + OUString name( OUString::createFromAscii( PyUnicode_AsUTF8( pyName.get() ) ) ); + TypeDescription desc( name ); + if( ! desc.is() ) + { + throw RuntimeException( "type " + name + " is unknown" ); + } + css::uno::TypeClass tc = *o3tl::doAccess<css::uno::TypeClass>(enumValue); + if( static_cast<css::uno::TypeClass>(desc.get()->eTypeClass) != tc ) + { + throw RuntimeException( "pyuno.checkType: " + name + " is a " + + OUString::createFromAscii( typeClassToString( static_cast<TypeClass>(desc.get()->eTypeClass)) ) + + ", but type got construct with typeclass " + + OUString::createFromAscii( typeClassToString( tc ) ) ); + } + return desc.get()->pWeakRef; +} + +static PyObject* callCtor( const Runtime &r , const char * clazz, const PyRef & args ) +{ + PyRef code( PyDict_GetItemString( r.getImpl()->cargo->getUnoModule().get(), clazz ) ); + if( ! code.is() ) + { + OString buf = OString::Concat("couldn't access uno.") + clazz; + PyErr_SetString( PyExc_RuntimeError, buf.getStr() ); + return nullptr; + } + PyRef instance( PyObject_CallObject( code.get(), args.get() ), SAL_NO_ACQUIRE); + Py_XINCREF( instance.get() ); + return instance.get(); + +} + + +PyObject *PyUNO_Enum_new( const char *enumBase, const char *enumValue, const Runtime &r ) +{ + PyRef args( PyTuple_New( 2 ), SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem( args.get() , 0 , PyUnicode_FromString( enumBase ) ); + PyTuple_SetItem( args.get() , 1 , PyUnicode_FromString( enumValue ) ); + + return callCtor( r, "Enum" , args ); +} + + +PyObject* PyUNO_Type_new (const char *typeName , TypeClass t , const Runtime &r ) +{ + // retrieve type object + PyRef args(PyTuple_New( 2 ), SAL_NO_ACQUIRE, NOT_NULL); + + PyTuple_SetItem( args.get() , 0 , PyUnicode_FromString( typeName ) ); + PyObject *typeClass = PyUNO_Enum_new( "com.sun.star.uno.TypeClass" , typeClassToString(t), r ); + if( ! typeClass ) + return nullptr; + PyTuple_SetItem( args.get() , 1 , typeClass); + + return callCtor( r, "Type" , args ); +} + +PyObject* PyUNO_char_new ( sal_Unicode val , const Runtime &r ) +{ + // retrieve type object + PyRef args( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL ); + static_assert(sizeof(sal_Unicode) == sizeof(Py_UCS2), "unexpected size"); + PyTuple_SetItem( args.get() , 0 , PyUnicode_FromKindAndData( PyUnicode_2BYTE_KIND, &val ,1) ); + + return callCtor( r, "Char" , args ); +} + +PyObject *PyUNO_ByteSequence_new( + const css::uno::Sequence< sal_Int8 > &byteSequence, const Runtime &r ) +{ + PyRef str( + PyBytes_FromStringAndSize( reinterpret_cast<char const *>(byteSequence.getConstArray()), byteSequence.getLength()), + SAL_NO_ACQUIRE ); + PyRef args( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL ); + PyTuple_SetItem( args.get() , 0 , str.getAcquired() ); + return callCtor( r, "ByteSequence" , args ); + +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/pyuno_util.cxx b/pyuno/source/module/pyuno_util.cxx new file mode 100644 index 0000000000..f01d33acfb --- /dev/null +++ b/pyuno/source/module/pyuno_util.cxx @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include "pyuno_impl.hxx" + +#include <osl/thread.h> +#include <osl/thread.hxx> + +#include <rtl/ustrbuf.hxx> +#include <osl/time.h> + +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Any; +using com::sun::star::uno::RuntimeException; + +namespace pyuno +{ +PyRef ustring2PyUnicode( const OUString & str ) +{ + PyRef ret; +#if Py_UNICODE_SIZE == 2 +#ifdef MACOSX + ret = PyRef( PyUnicode_FromUnicode( reinterpret_cast<const unsigned short *>(str.getStr()), str.getLength() ), SAL_NO_ACQUIRE ); +#else + static_assert(sizeof (wchar_t) == Py_UNICODE_SIZE, "bad assumption"); + ret = PyRef( PyUnicode_FromUnicode( reinterpret_cast<wchar_t const *>(str.getStr()), str.getLength() ), SAL_NO_ACQUIRE ); +#endif +#else + OString sUtf8(OUStringToOString(str, RTL_TEXTENCODING_UTF8)); + ret = PyRef( PyUnicode_DecodeUTF8( sUtf8.getStr(), sUtf8.getLength(), nullptr) , SAL_NO_ACQUIRE ); +#endif + return ret; +} + +PyRef ustring2PyString( std::u16string_view str ) +{ + OString o = OUStringToOString( str, osl_getThreadTextEncoding() ); + return PyRef( PyUnicode_FromString( o.getStr() ), SAL_NO_ACQUIRE ); +} + +OUString pyString2ustring( PyObject *pystr ) +{ + OUString ret; + if( PyUnicode_Check( pystr ) ) + { +#if Py_UNICODE_SIZE == 2 + ret = OUString( + reinterpret_cast<sal_Unicode const *>(PyUnicode_AS_UNICODE( pystr )) ); +#else + Py_ssize_t size(0); + char const *pUtf8(PyUnicode_AsUTF8AndSize(pystr, &size)); + ret = OUString(pUtf8, size, RTL_TEXTENCODING_UTF8); +#endif + } + else + { + char *name = PyBytes_AsString(pystr); // hmmm... is this a good idea? + ret = OUString( name, strlen(name), osl_getThreadTextEncoding() ); + } + return ret; +} + +PyRef getObjectFromUnoModule( const Runtime &runtime, const char * func ) +{ + PyRef object(PyDict_GetItemString( runtime.getImpl()->cargo->getUnoModule().get(), func ) ); + if( !object.is() ) + { + throw RuntimeException("couldn't find core function " + OUString::createFromAscii(func)); + } + return object; +} + + +// Logging + + +bool isLog( RuntimeCargo const * cargo, sal_Int32 loglevel ) +{ + return cargo && cargo->logFile && loglevel <= cargo->logLevel; +} + +void log( RuntimeCargo * cargo, sal_Int32 level, std::u16string_view logString ) +{ + log( cargo, level, OUStringToOString( logString, osl_getThreadTextEncoding() ).getStr() ); +} + +void log( RuntimeCargo * cargo, sal_Int32 level, const char *str ) +{ + if( !isLog( cargo, level ) ) + return; + + static const char *strLevel[] = { "NONE", "CALL", "ARGS" }; + + TimeValue systemTime; + TimeValue localTime; + oslDateTime localDateTime; + + osl_getSystemTime( &systemTime ); + osl_getLocalTimeFromSystemTime( &systemTime, &localTime ); + osl_getDateTimeFromTimeValue( &localTime, &localDateTime ); + + fprintf( cargo->logFile, + "%4i-%02i-%02i %02i:%02i:%02i,%03lu [%s,tid %ld]: %s\n", + localDateTime.Year, + localDateTime.Month, + localDateTime.Day, + localDateTime.Hours, + localDateTime.Minutes, + localDateTime.Seconds, + sal::static_int_cast< unsigned long >( + localDateTime.NanoSeconds/1000000), + strLevel[level], + sal::static_int_cast< long >( + static_cast<sal_Int32>(osl::Thread::getCurrentIdentifier())), + str ); +} + +namespace { + +void appendPointer(OUStringBuffer & buffer, void * pointer) { + buffer.append( + sal::static_int_cast< sal_Int64 >( + reinterpret_cast< sal_IntPtr >(pointer)), + 16); +} + +} + +void logException( RuntimeCargo *cargo, const char *intro, + void * ptr, std::u16string_view aFunctionName, + const void * data, const css::uno::Type & type ) +{ + if( isLog( cargo, LogLevel::CALL ) ) + { + OUStringBuffer buf( 128 ); + buf.appendAscii( intro ); + appendPointer(buf, ptr); + buf.append( OUString::Concat("].") + aFunctionName + " = " ); + buf.append( + val2str( data, type.getTypeLibType(), VAL2STR_MODE_SHALLOW ) ); + log( cargo,LogLevel::CALL, buf ); + } + +} + +void logReply( + RuntimeCargo *cargo, + const char *intro, + void * ptr, + std::u16string_view aFunctionName, + const Any &returnValue, + const Sequence< Any > & aParams ) +{ + OUStringBuffer buf( 128 ); + buf.appendAscii( intro ); + appendPointer(buf, ptr); + buf.append( OUString::Concat("].") + aFunctionName + "()=" ); + if( isLog( cargo, LogLevel::ARGS ) ) + { + buf.append( + val2str( returnValue.getValue(), returnValue.getValueTypeRef(), VAL2STR_MODE_SHALLOW) ); + for( const auto & p : aParams ) + { + buf.append( ", " + val2str( p.getValue(), p.getValueTypeRef(), VAL2STR_MODE_SHALLOW) ); + } + } + log( cargo,LogLevel::CALL, buf ); + +} + +void logCall( RuntimeCargo *cargo, const char *intro, + void * ptr, std::u16string_view aFunctionName, + const Sequence< Any > & aParams ) +{ + OUStringBuffer buf( 128 ); + buf.appendAscii( intro ); + appendPointer(buf, ptr); + buf.append( OUString::Concat("].") + aFunctionName + "(" ); + if( isLog( cargo, LogLevel::ARGS ) ) + { + for( int i = 0; i < aParams.getLength() ; i ++ ) + { + if( i > 0 ) + buf.append( ", " ); + buf.append( + val2str( aParams[i].getValue(), aParams[i].getValueTypeRef(), VAL2STR_MODE_SHALLOW) ); + } + } + buf.append( ")" ); + log( cargo,LogLevel::CALL, buf ); +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/uno.py b/pyuno/source/module/uno.py new file mode 100644 index 0000000000..b2a75bd039 --- /dev/null +++ b/pyuno/source/module/uno.py @@ -0,0 +1,553 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# +import pyuno +import sys +import traceback +import warnings + +# since on Windows sal3.dll no longer calls WSAStartup +import socket + +# All functions and variables starting with a underscore (_) must be +# considered private and can be changed at any time. Don't use them. +_component_context = pyuno.getComponentContext() + + +def getComponentContext(): + """Returns the UNO component context used to initialize the Python runtime.""" + + return _component_context + + +def getCurrentContext(): + """Returns the current context. + + See http://udk.openoffice.org/common/man/concept/uno_contexts.html#current_context + for an explanation on the current context concept. + """ + + return pyuno.getCurrentContext() + + +def setCurrentContext(newContext): + """Sets newContext as new UNO context. + + The newContext must implement the XCurrentContext interface. The + implementation should handle the desired properties and delegate + unknown properties to the old context. Ensure that the old one + is reset when you leave your stack, see + http://udk.openoffice.org/common/man/concept/uno_contexts.html#current_context + """ + + return pyuno.setCurrentContext(newContext) + + +def getConstantByName(constant): + """Looks up the value of an IDL constant by giving its explicit name.""" + + return pyuno.getConstantByName(constant) + + +def getTypeByName(typeName): + """Returns a `uno.Type` instance of the type given by typeName. + + If the type does not exist, a `com.sun.star.uno.RuntimeException` is raised. + """ + + return pyuno.getTypeByName(typeName) + + +def createUnoStruct(typeName, *args, **kwargs): + """Creates a UNO struct or exception given by typeName. + + Can be called with: + + 1) No additional argument. + In this case, you get a default constructed UNO structure. + (e.g. `createUnoStruct("com.sun.star.uno.Exception")`) + 2) Exactly one additional argument that is an instance of typeName. + In this case, a copy constructed instance of typeName is returned + (e.g. `createUnoStruct("com.sun.star.uno.Exception" , e)`) + 3) As many additional arguments as the number of elements within typeName + (e.g. `createUnoStruct("com.sun.star.uno.Exception", "foo error" , self)`). + 4) Keyword arguments to give values for each element of the struct by name. + 5) A mix of 3) and 4), such that each struct element is given a value exactly once, + either by a positional argument or by a keyword argument. + + The additional and/or keyword arguments must match the type of each struct element, + otherwise an exception is thrown. + """ + + return getClass(typeName)(*args, **kwargs) + + +def getClass(typeName): + """Returns the class of a concrete UNO exception, struct, or interface.""" + + return pyuno.getClass(typeName) + + +def isInterface(obj): + """Returns True, when obj is a class of a UNO interface.""" + + return pyuno.isInterface(obj) + + +def generateUuid(): + """Returns a 16 byte sequence containing a newly generated uuid or guid. + + For more information, see rtl/uuid.h. + """ + + return pyuno.generateUuid() + + +def systemPathToFileUrl(systemPath): + """Returns a file URL for the given system path.""" + + return pyuno.systemPathToFileUrl(systemPath) + + +def fileUrlToSystemPath(url): + """Returns a system path. + + This path is determined by the system that the Python interpreter is running on. + """ + + return pyuno.fileUrlToSystemPath(url) + + +def absolutize(path, relativeUrl): + """Returns an absolute file url from the given urls.""" + + return pyuno.absolutize(path, relativeUrl) + + +class Enum: + """Represents a UNO enum. + + Use an instance of this class to explicitly pass an enum to UNO. + + :param typeName: The name of the enum as a string. + :param value: The actual value of this enum as a string. + """ + + def __init__(self, typeName, value): + self.typeName = typeName + self.value = value + pyuno.checkEnum(self) + + def __repr__(self): + return "<Enum instance %s (%r)>" % (self.typeName, self.value) + + def __eq__(self, that): + if not isinstance(that, Enum): + return False + + return (self.typeName == that.typeName) and (self.value == that.value) + + def __ne__(self,other): + return not self.__eq__(other) + + +class Type: + """Represents a UNO type. + + Use an instance of this class to explicitly pass a type to UNO. + + :param typeName: Name of the UNO type + :param typeClass: Python Enum of TypeClass, see com/sun/star/uno/TypeClass.idl + """ + + def __init__(self, typeName, typeClass): + self.typeName = typeName + self.typeClass = typeClass + pyuno.checkType(self) + + def __repr__(self): + return "<Type instance %s (%r)>" % (self.typeName, self.typeClass) + + def __eq__(self, that): + if not isinstance(that, Type): + return False + + return self.typeClass == that.typeClass and self.typeName == that.typeName + + def __ne__(self,other): + return not self.__eq__(other) + + def __hash__(self): + return self.typeName.__hash__() + + +class Bool(object): + """Represents a UNO boolean. + + Use an instance of this class to explicitly pass a boolean to UNO. + + Note: This class is deprecated. Use Python's True and False directly instead. + """ + + def __new__(cls, value): + message = "The Bool class is deprecated. Use Python's True and False directly instead." + warnings.warn(message, DeprecationWarning) + + if isinstance(value, str) and value == "true": + return True + + if isinstance(value, str) and value == "false": + return False + + if value: + return True + + return False + + +class Char: + """Represents a UNO char. + + Use an instance of this class to explicitly pass a char to UNO. + + For Python 3, this class only works with unicode (str) objects. Creating + a Char instance with a bytes object or comparing a Char instance + to a bytes object will raise an AssertionError. + + :param value: A Unicode string with length 1 + """ + + def __init__(self, value): + assert isinstance(value, str), "Expected str object, got %s instead." % type(value) + + assert len(value) == 1, "Char value must have length of 1." + assert ord(value[0]) <= 0xFFFF, "Char value must be UTF-16 code unit" + + self.value = value + + def __repr__(self): + return "<Char instance %s>" % (self.value,) + + def __eq__(self, that): + if isinstance(that, str): + if len(that) > 1: + return False + + return self.value == that[0] + + if isinstance(that, Char): + return self.value == that.value + + return False + + def __ne__(self,other): + return not self.__eq__(other) + + +class ByteSequence: + """Represents a UNO ByteSequence value. + + Use an instance of this class to explicitly pass a byte sequence to UNO. + + :param value: A string or bytesequence + """ + + def __init__(self, value): + if isinstance(value, bytes): + self.value = value + + elif isinstance(value, ByteSequence): + self.value = value.value + + else: + raise TypeError("Expected bytes object or ByteSequence, got %s instead." % type(value)) + + def __repr__(self): + return "<ByteSequence instance '%s'>" % (self.value,) + + def __eq__(self, that): + if isinstance(that, bytes): + return self.value == that + + if isinstance(that, ByteSequence): + return self.value == that.value + + return False + + def __len__(self): + return len(self.value) + + def __getitem__(self, index): + return self.value[index] + + def __iter__(self): + return self.value.__iter__() + + def __add__(self, b): + if isinstance(b, bytes): + return ByteSequence(self.value + b) + + elif isinstance(b, ByteSequence): + return ByteSequence(self.value + b.value) + + else: + raise TypeError("Can't add ByteString and %s." % type(b)) + + def __hash__(self): + return self.value.hash() + + +class Any: + """Represents a UNO Any value. + + Use only in connection with uno.invoke() to pass an explicit typed Any. + """ + + def __init__(self, type, value): + if isinstance(type, Type): + self.type = type + else: + self.type = getTypeByName(type) + + self.value = value + + +def invoke(object, methodname, argTuple): + """Use this function to pass exactly typed Anys to the callee (using uno.Any).""" + + return pyuno.invoke(object, methodname, argTuple) + + +# ----------------------------------------------------------------------------- +# Don't use any functions beyond this point; private section, likely to change. +# ----------------------------------------------------------------------------- +_builtin_import = __import__ + + +def _uno_import(name, *optargs, **kwargs): + """Overrides built-in import to allow directly importing LibreOffice classes.""" + + try: + return _builtin_import(name, *optargs, **kwargs) + except ImportError as e: + # process optargs + globals, locals, fromlist = list(optargs)[:3] + [kwargs.get('globals', {}), kwargs.get('locals', {}), + kwargs.get('fromlist', [])][len(optargs):] + + # from import form only, but skip if a uno lookup has already failed + if not fromlist or hasattr(e, '_uno_import_failed'): + raise + + # hang onto exception for possible use on subsequent uno lookup failure + py_import_exc = e + + mod = None + d = sys.modules + + for module in name.split("."): + if module in d: + mod = d[module] + else: + # How to create a module ?? + mod = pyuno.__class__(module) + if mod is None: + raise py_import_exc + + d = mod.__dict__ + + RuntimeException = pyuno.getClass("com.sun.star.uno.RuntimeException") + + for class_name in fromlist: + if class_name not in d: + failed = False + + try: + # check for structs, exceptions or interfaces + d[class_name] = pyuno.getClass(name + "." + class_name) + except RuntimeException: + # check for enums + try: + d[class_name] = Enum(name, class_name) + except RuntimeException: + # check for constants + try: + d[class_name] = getConstantByName(name + "." + class_name) + except RuntimeException: + # check for constant group + try: + d[class_name] = _impl_getConstantGroupByName(name, class_name) + except ValueError: + failed = True + + if failed: + # We have an import failure, but cannot distinguish between + # uno and non-uno errors as uno lookups are attempted for all + # "from xxx import yyy" imports following a python failure. + # + # In Python 3, the original python exception traceback is reused + # to help pinpoint the actual failing location. Its original + # message, unlike Python 2, is unlikely to be helpful for uno + # failures, as it most commonly is just a top level module like + # 'com'. So our exception appends the uno lookup failure. + # This is more ambiguous, but it plus the traceback should be + # sufficient to identify a root cause for python or uno issues. + # + # Our exception is raised outside of the nested exception + # handlers above, to avoid Python 3 nested exception + # information for the RuntimeExceptions during lookups. + # + # Finally, a private attribute is used to prevent further + # processing if this failure was in a nested import. That + # keeps the exception relevant to the primary failure point, + # preventing us from re-processing our own import errors. + + uno_import_exc = ImportError("%s (or '%s.%s' is unknown)" % + (py_import_exc, name, class_name)) + + uno_import_exc = uno_import_exc.with_traceback(py_import_exc.__traceback__) + + uno_import_exc._uno_import_failed = True + raise uno_import_exc + + return mod + + +try: + import __builtin__ +except ImportError: + import builtins as __builtin__ + +# hook into the __import__ chain +__builtin__.__dict__['__import__'] = _uno_import + + +class _ConstantGroup(object): + """Represents a group of UNOIDL constants.""" + + __slots__ = ['_constants'] + + def __init__(self, constants): + self._constants = constants + + def __dir__(self): + return self._constants.keys() + + def __getattr__(self, name): + if name in self._constants: + return self._constants[name] + + raise AttributeError("The constant '%s' could not be found." % name) + + +def _impl_getConstantGroupByName(module, group): + """Gets UNOIDL constant group by name.""" + + constants = Enum('com.sun.star.uno.TypeClass', 'CONSTANTS') + one = Enum('com.sun.star.reflection.TypeDescriptionSearchDepth', 'ONE') + type_desc_mgr = _component_context.getValueByName('/singletons/com.sun.star.reflection.theTypeDescriptionManager') + type_descs = type_desc_mgr.createTypeDescriptionEnumeration(module, (constants,), one) + qualified_name = module + '.' + group + + for type_desc in type_descs: + if type_desc.Name == qualified_name: + return _ConstantGroup(dict( + (c.Name.split('.')[-1], c.ConstantValue) + for c in type_desc.Constants)) + + raise ValueError("The constant group '%s' could not be found." % qualified_name) + + +def _uno_struct__init__(self, *args, **kwargs): + """Initializes a UNO struct. + + Referenced from the pyuno shared library. + + This function can be called with either an already constructed UNO struct, which it + will then just reference without copying, or with arguments to create a new UNO struct. + """ + + # Check to see if this function was passed an existing UNO struct + if len(kwargs) == 0 and len(args) == 1 and getattr(args[0], "__class__", None) == self.__class__: + self.__dict__['value'] = args[0] + else: + struct, used = pyuno._createUnoStructHelper(self.__class__.__pyunostruct__, args, **kwargs) + + for kwarg in kwargs.keys(): + if not used.get(kwarg): + RuntimeException = pyuno.getClass("com.sun.star.uno.RuntimeException") + raise RuntimeException("_uno_struct__init__: unused keyword argument '%s'." % kwarg, None) + + self.__dict__["value"] = struct + + +def _uno_struct__getattr__(self, name): + """Gets attribute from UNO struct. + + Referenced from the pyuno shared library. + """ + + return getattr(self.__dict__["value"], name) + + +def _uno_struct__setattr__(self, name, value): + """Sets attribute on UNO struct. + + Referenced from the pyuno shared library. + """ + + return setattr(self.__dict__["value"], name, value) + + +def _uno_struct__repr__(self): + """Converts a UNO struct to a printable string. + + Referenced from the pyuno shared library. + """ + + return repr(self.__dict__["value"]) + + +def _uno_struct__str__(self): + """Converts a UNO struct to a string.""" + + return str(self.__dict__["value"]) + +def _uno_struct__ne__(self, other): + return not self.__eq__(other) + +def _uno_struct__eq__(self, that): + """Compares two UNO structs. + + Referenced from the pyuno shared library. + """ + + if hasattr(that, "value"): + return self.__dict__["value"] == that.__dict__["value"] + + return False + + +def _uno_extract_printable_stacktrace(trace): + """Extracts a printable stacktrace. + + Referenced from pyuno shared lib and pythonscript.py. + """ + + return ''.join(traceback.format_tb(trace)) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/source/module/unohelper.py b/pyuno/source/module/unohelper.py new file mode 100644 index 0000000000..cf6f78d63b --- /dev/null +++ b/pyuno/source/module/unohelper.py @@ -0,0 +1,297 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# +import uno +import pyuno +import os +import sys + +from com.sun.star.lang import XTypeProvider, XSingleComponentFactory, XServiceInfo +from com.sun.star.uno import RuntimeException, XCurrentContext +from com.sun.star.beans.MethodConcept import ALL as METHOD_CONCEPT_ALL +from com.sun.star.beans.PropertyConcept import ALL as PROPERTY_CONCEPT_ALL + +from com.sun.star.reflection.ParamMode import \ + IN as PARAM_MODE_IN, \ + OUT as PARAM_MODE_OUT, \ + INOUT as PARAM_MODE_INOUT + +from com.sun.star.beans.PropertyAttribute import \ + MAYBEVOID as PROP_ATTR_MAYBEVOID, \ + BOUND as PROP_ATTR_BOUND, \ + CONSTRAINED as PROP_ATTR_CONSTRAINED, \ + TRANSIENT as PROP_ATTR_TRANSIENT, \ + READONLY as PROP_ATTR_READONLY, \ + MAYBEAMBIGUOUS as PROP_ATTR_MAYBEAMBIGUOUS, \ + MAYBEDEFAULT as PROP_ATTR_MAYBEDEFAULT, \ + REMOVABLE as PROP_ATTR_REMOVABLE + +def _mode_to_str( mode ): + ret = "[]" + if mode == PARAM_MODE_INOUT: + ret = "[inout]" + elif mode == PARAM_MODE_OUT: + ret = "[out]" + elif mode == PARAM_MODE_IN: + ret = "[in]" + return ret + +def _propertymode_to_str( mode ): + ret = "" + if PROP_ATTR_REMOVABLE & mode: + ret = ret + "removable " + if PROP_ATTR_MAYBEDEFAULT & mode: + ret = ret + "maybedefault " + if PROP_ATTR_MAYBEAMBIGUOUS & mode: + ret = ret + "maybeambiguous " + if PROP_ATTR_READONLY & mode: + ret = ret + "readonly " + if PROP_ATTR_TRANSIENT & mode: + ret = ret + "transient " + if PROP_ATTR_CONSTRAINED & mode: + ret = ret + "constrained " + if PROP_ATTR_BOUND & mode: + ret = ret + "bound " + if PROP_ATTR_MAYBEVOID & mode: + ret = ret + "maybevoid " + return ret.rstrip() + +def inspect( obj , out ): + if isinstance( obj, uno.Type ) or \ + isinstance( obj, uno.Char ) or \ + isinstance( obj, uno.Bool ) or \ + isinstance( obj, uno.ByteSequence ) or \ + isinstance( obj, uno.Enum ) or \ + isinstance( obj, uno.Any ): + out.write( str(obj) + "\n") + return + + ctx = uno.getComponentContext() + introspection = \ + ctx.ServiceManager.createInstanceWithContext( "com.sun.star.beans.Introspection", ctx ) + + out.write( "Supported services:\n" ) + if hasattr( obj, "getSupportedServiceNames" ): + names = obj.getSupportedServiceNames() + for ii in names: + out.write( " " + ii + "\n" ) + else: + out.write( " unknown\n" ) + + out.write( "Interfaces:\n" ) + if hasattr( obj, "getTypes" ): + interfaces = obj.getTypes() + for ii in interfaces: + out.write( " " + ii.typeName + "\n" ) + else: + out.write( " unknown\n" ) + + access = introspection.inspect( obj ) + methods = access.getMethods( METHOD_CONCEPT_ALL ) + out.write( "Methods:\n" ) + for ii in methods: + out.write( " " + ii.ReturnType.Name + " " + ii.Name ) + args = ii.ParameterTypes + infos = ii.ParameterInfos + out.write( "( " ) + for i in range( 0, len( args ) ): + if i > 0: + out.write( ", " ) + out.write( _mode_to_str( infos[i].aMode ) + " " + args[i].Name + " " + infos[i].aName ) + out.write( " )\n" ) + + props = access.getProperties( PROPERTY_CONCEPT_ALL ) + out.write ("Properties:\n" ) + for ii in props: + out.write( " ("+_propertymode_to_str( ii.Attributes ) + ") "+ii.Type.typeName+" "+ii.Name+ "\n" ) + +def createSingleServiceFactory( clazz, implementationName, serviceNames ): + return _FactoryHelper_( clazz, implementationName, serviceNames ) + +class _ImplementationHelperEntry: + def __init__(self, ctor,serviceNames): + self.ctor = ctor + self.serviceNames = serviceNames + +class ImplementationHelper: + def __init__(self): + self.impls = {} + + def addImplementation( self, ctor, implementationName, serviceNames ): + self.impls[implementationName] = _ImplementationHelperEntry(ctor,serviceNames) + + def writeRegistryInfo( self, regKey, smgr ): + for i in list(self.impls.items()): + keyName = "/"+ i[0] + "/UNO/SERVICES" + key = regKey.createKey( keyName ) + for serviceName in i[1].serviceNames: + key.createKey( serviceName ) + return 1 + + def getComponentFactory( self, implementationName , regKey, smgr ): + entry = self.impls.get( implementationName, None ) + if entry is None: + raise RuntimeException( implementationName + " is unknown" , None ) + return createSingleServiceFactory( entry.ctor, implementationName, entry.serviceNames ) + + def getSupportedServiceNames( self, implementationName ): + entry = self.impls.get( implementationName, None ) + if entry is None: + raise RuntimeException( implementationName + " is unknown" , None ) + return entry.serviceNames + + def supportsService( self, implementationName, serviceName ): + entry = self.impls.get( implementationName,None ) + if entry is None: + raise RuntimeException( implementationName + " is unknown", None ) + return serviceName in entry.serviceNames + + +class ImplementationEntry: + def __init__(self, implName, supportedServices, clazz ): + self.implName = implName + self.supportedServices = supportedServices + self.clazz = clazz + +def writeRegistryInfoHelper( smgr, regKey, seqEntries ): + for entry in seqEntries: + keyName = "/"+ entry.implName + "/UNO/SERVICES" + key = regKey.createKey( keyName ) + for serviceName in entry.supportedServices: + key.createKey( serviceName ) + +def systemPathToFileUrl( systemPath ): + "returns a file-url for the given system path" + return pyuno.systemPathToFileUrl( systemPath ) + +def fileUrlToSystemPath( url ): + "returns a system path (determined by the system, the python interpreter is running on)" + return pyuno.fileUrlToSystemPath( url ) + +def absolutize( path, relativeUrl ): + "returns an absolute file url from the given urls" + return pyuno.absolutize( path, relativeUrl ) + +def getComponentFactoryHelper( implementationName, smgr, regKey, seqEntries ): + for x in seqEntries: + if x.implName == implementationName: + return createSingleServiceFactory( x.clazz, implementationName, x.supportedServices ) + +def addComponentsToContext( toBeExtendedContext, contextRuntime, componentUrls, loaderName ): + smgr = contextRuntime.ServiceManager + loader = smgr.createInstanceWithContext( loaderName, contextRuntime ) + implReg = smgr.createInstanceWithContext( "com.sun.star.registry.ImplementationRegistration",contextRuntime) + + isWin = os.name == 'nt' or os.name == 'dos' + isMac = sys.platform == 'darwin' + # create a temporary registry + for componentUrl in componentUrls: + reg = smgr.createInstanceWithContext( "com.sun.star.registry.SimpleRegistry", contextRuntime ) + reg.open( "", 0, 1 ) + if not isWin and componentUrl.endswith( ".uno" ): # still allow platform independent naming + if isMac: + componentUrl = componentUrl + ".dylib" + else: + componentUrl = componentUrl + ".so" + + implReg.registerImplementation( loaderName,componentUrl, reg ) + rootKey = reg.getRootKey() + implementationKey = rootKey.openKey( "IMPLEMENTATIONS" ) + implNames = implementationKey.getKeyNames() + extSMGR = toBeExtendedContext.ServiceManager + for x in implNames: + fac = loader.activate( max(x.split("/")),"",componentUrl,rootKey) + extSMGR.insert( fac ) + reg.close() + +# never shrinks ! +_g_typeTable = {} +def _unohelper_getHandle( self): + ret = None + if self.__class__ in _g_typeTable: + ret = _g_typeTable[self.__class__] + else: + names = {} + traverse = list(self.__class__.__bases__) + while len( traverse ) > 0: + item = traverse.pop() + bases = item.__bases__ + if uno.isInterface( item ): + names[item.__pyunointerface__] = None + elif len(bases) > 0: + # the "else if", because we only need the most derived interface + traverse = traverse + list(bases)# + + lst = list(names.keys()) + types = [] + for x in lst: + t = uno.getTypeByName( x ) + types.append( t ) + + ret = tuple(types) + _g_typeTable[self.__class__] = ret + return ret + +class Base(XTypeProvider): + def getTypes( self ): + return _unohelper_getHandle( self ) + def getImplementationId(self): + return () + +class CurrentContext(XCurrentContext, Base ): + """a current context implementation, which first does a lookup in the given + hashmap and if the key cannot be found, it delegates to the predecessor + if available + """ + def __init__( self, oldContext, hashMap ): + self.hashMap = hashMap + self.oldContext = oldContext + + def getValueByName( self, name ): + if name in self.hashMap: + return self.hashMap[name] + elif self.oldContext is not None: + return self.oldContext.getValueByName( name ) + else: + return None + +# ------------------------------------------------- +# implementation details +# ------------------------------------------------- +class _FactoryHelper_( XSingleComponentFactory, XServiceInfo, Base ): + def __init__( self, clazz, implementationName, serviceNames ): + self.clazz = clazz + self.implementationName = implementationName + self.serviceNames = serviceNames + + def getImplementationName( self ): + return self.implementationName + + def supportsService( self, ServiceName ): + return ServiceName in self.serviceNames + + def getSupportedServiceNames( self ): + return self.serviceNames + + def createInstanceWithContext( self, context ): + return self.clazz( context ) + + def createInstanceWithArgumentsAndContext( self, args, context ): + return self.clazz( context, *args ) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/source/officehelper.py b/pyuno/source/officehelper.py new file mode 100644 index 0000000000..53bd5943e3 --- /dev/null +++ b/pyuno/source/officehelper.py @@ -0,0 +1,87 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +# +# Translated to python from "Bootstrap.java" by Kim Kulak +# + +import os +import random +from sys import platform +from time import sleep + +import uno +from com.sun.star.connection import NoConnectException +from com.sun.star.uno import Exception as UnoException + + +class BootstrapException(UnoException): + pass + +def bootstrap(): + """Bootstrap OOo and PyUNO Runtime. + The soffice process is started opening a named pipe of random name, then the local context is used + to access the pipe. This function directly returns the remote component context, from whereon you can + get the ServiceManager by calling getServiceManager() on the returned object. + """ + try: + # soffice script used on *ix, Mac; soffice.exe used on Win + if "UNO_PATH" in os.environ: + sOffice = os.environ["UNO_PATH"] + else: + sOffice = "" # lets hope for the best + sOffice = os.path.join(sOffice, "soffice") + if platform.startswith("win"): + sOffice += ".exe" + + # Generate a random pipe name. + random.seed() + sPipeName = "uno" + str(random.random())[2:] + + # Start the office process, don't check for exit status since an exception is caught anyway if the office terminates unexpectedly. + cmdArray = (sOffice, "--nologo", "--nodefault", "".join(["--accept=pipe,name=", sPipeName, ";urp;"])) + os.spawnv(os.P_NOWAIT, sOffice, cmdArray) + + # --------- + + xLocalContext = uno.getComponentContext() + resolver = xLocalContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", xLocalContext) + sConnect = "".join(["uno:pipe,name=", sPipeName, ";urp;StarOffice.ComponentContext"]) + + # Wait until an office is started, but loop only nLoop times (can we do this better???) + nLoop = 20 + while True: + try: + xContext = resolver.resolve(sConnect) + break + except NoConnectException: + nLoop -= 1 + if nLoop <= 0: + raise BootstrapException("Cannot connect to soffice server.", None) + sleep(0.5) # Sleep 1/2 second. + + except BootstrapException: + raise + except Exception as e: # Any other exception + raise BootstrapException("Caught exception " + str(e), None) + + return xContext + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/pyuno/zipcore/mac.sh b/pyuno/zipcore/mac.sh new file mode 100644 index 0000000000..830bcdaa10 --- /dev/null +++ b/pyuno/zipcore/mac.sh @@ -0,0 +1,14 @@ +# Set URE_BOOTSTRAP so that "uno.getComponentContext()" bootstraps a complete +# OOo UNO environment: +: ${URE_BOOTSTRAP=vnd.sun.star.pathname:$sd_prog/../Resources/fundamentalrc} +export URE_BOOTSTRAP + +PYTHONHOME=$sd_prog/../Frameworks/LibreOfficePython.framework +export PYTHONHOME + +pybasislibdir=$PYTHONHOME/Versions/%%PYVERSION%%/lib/python%%PYVERSION%% +PYTHONPATH=$sd_prog/../Resources:$sd_prog/../Frameworks:$pybasislibdir:$pybasislibdir/lib-dynload:$pybasislibdir/lib-tk:$pybasislibdir/site-packages${PYTHONPATH+:$PYTHONPATH} +export PYTHONPATH + +# execute binary +exec "$PYTHONHOME/Versions/%%PYVERSION%%/Resources/Python.app/Contents/MacOS/LibreOfficePython" "$@" diff --git a/pyuno/zipcore/nonmac.sh b/pyuno/zipcore/nonmac.sh new file mode 100644 index 0000000000..5e7cca14d0 --- /dev/null +++ b/pyuno/zipcore/nonmac.sh @@ -0,0 +1,12 @@ +# Set URE_BOOTSTRAP so that "uno.getComponentContext()" bootstraps a complete +# OOo UNO environment: +: ${URE_BOOTSTRAP=vnd.sun.star.pathname:$sd_prog/fundamentalrc} +export URE_BOOTSTRAP + +PYTHONPATH=$sd_prog:$sd_prog/python-core-%%PYVERSION%%/lib:$sd_prog/python-core-%%PYVERSION%%/lib/lib-dynload:$sd_prog/python-core-%%PYVERSION%%/lib/lib-tk:$sd_prog/python-core-%%PYVERSION%%/lib/site-packages${PYTHONPATH+:$PYTHONPATH} +export PYTHONPATH +PYTHONHOME=$sd_prog/python-core-%%PYVERSION%% +export PYTHONHOME + +# execute binary +exec "$sd_prog/python.bin" "$@" diff --git a/pyuno/zipcore/python.cxx b/pyuno/zipcore/python.cxx new file mode 100644 index 0000000000..7580e32789 --- /dev/null +++ b/pyuno/zipcore/python.cxx @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + +#include <config_python.h> + +#include <cstddef> +#include <stdlib.h> +#include <wchar.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <tools/pathutils.hxx> + +#define MY_LENGTH(s) (sizeof (s) / sizeof *(s) - 1) +#define MY_STRING(s) (s), MY_LENGTH(s) + +static wchar_t * encode(wchar_t * buffer, wchar_t const * text) { + *buffer++ = L'"'; + std::size_t n = 0; + for (;;) { + wchar_t c = *text++; + if (c == L'\0') { + break; + } else if (c == L'"') { + // Double any preceding backslashes as required by Windows: + for (std::size_t i = 0; i < n; ++i) { + *buffer++ = L'\\'; + } + *buffer++ = L'\\'; + *buffer++ = L'"'; + n = 0; + } else if (c == L'\\') { + *buffer++ = L'\\'; + ++n; + } else { + *buffer++ = c; + n = 0; + } + } + // The command line will continue with a double quote, so double any + // preceding backslashes as required by Windows: + for (std::size_t i = 0; i < n; ++i) { + *buffer++ = L'\\'; + } + *buffer++ = L'"'; + return buffer; +} + +int wmain(int argc, wchar_t ** argv, wchar_t **) { + wchar_t path[MAX_PATH]; + DWORD n = GetModuleFileNameW(nullptr, path, MAX_PATH); + if (n == 0 || n >= MAX_PATH) { + exit(EXIT_FAILURE); + } + wchar_t * pathEnd = tools::filename(path); + *pathEnd = L'\0'; + n = GetEnvironmentVariableW(L"UNO_PATH", nullptr, 0); + if (n == 0) { + if (GetLastError() != ERROR_ENVVAR_NOT_FOUND || + !SetEnvironmentVariableW(L"UNO_PATH", path)) + { + exit(EXIT_FAILURE); + } + } + wchar_t bootstrap[MY_LENGTH(L"vnd.sun.star.pathname:") + MAX_PATH] = + L"vnd.sun.star.pathname:"; //TODO: overflow + wchar_t * bootstrapEnd = tools::buildPath( + bootstrap + MY_LENGTH(L"vnd.sun.star.pathname:"), path, pathEnd, + MY_STRING(L"fundamental.ini")); + if (bootstrapEnd == nullptr) { + exit(EXIT_FAILURE); + } + wchar_t pythonpath2[MAX_PATH]; + wchar_t * pythonpath2End = tools::buildPath( + pythonpath2, path, pathEnd, + MY_STRING(L"\\python-core-" PYTHON_VERSION_STRING L"\\lib")); + if (pythonpath2End == nullptr) { + exit(EXIT_FAILURE); + } + wchar_t pythonpath3[MAX_PATH]; + wchar_t * pythonpath3End = tools::buildPath( + pythonpath3, path, pathEnd, + MY_STRING(L"\\python-core-" PYTHON_VERSION_STRING L"\\lib\\site-packages")); + if (pythonpath3End == nullptr) { + exit(EXIT_FAILURE); + } + wchar_t pythonhome[MAX_PATH]; + wchar_t * pythonhomeEnd = tools::buildPath( + pythonhome, path, pathEnd, MY_STRING(L"\\python-core-" PYTHON_VERSION_STRING)); + if (pythonhomeEnd == nullptr) { + exit(EXIT_FAILURE); + } + wchar_t pythonexe[MAX_PATH]; + wchar_t * pythonexeEnd = tools::buildPath( + pythonexe, path, pathEnd, + MY_STRING(L"\\python-core-" PYTHON_VERSION_STRING L"\\bin\\python.exe")); + if (pythonexeEnd == nullptr) { + exit(EXIT_FAILURE); + } + std::size_t clSize = MY_LENGTH(L"\"") + 4 * (pythonexeEnd - pythonexe) + + MY_LENGTH(L"\"\0"); //TODO: overflow + // 4 * len: each char preceded by backslash, each trailing backslash + // doubled + for (int i = 1; i < argc; ++i) { + clSize += MY_LENGTH(L" \"") + 4 * wcslen(argv[i]) + MY_LENGTH(L"\""); + //TODO: overflow + } + wchar_t * cl = new wchar_t[clSize]; + wchar_t * cp = encode(cl, pythonhome); + for (int i = 1; i < argc; ++i) { + *cp++ = L' '; + cp = encode(cp, argv[i]); + } + *cp = L'\0'; + n = GetEnvironmentVariableW(L"PATH", nullptr, 0); + wchar_t * orig; + if (n == 0) { + if (GetLastError() != ERROR_ENVVAR_NOT_FOUND) { + exit(EXIT_FAILURE); + } + orig = const_cast<wchar_t *>(L""); + } else { + orig = new wchar_t[n]; + if (GetEnvironmentVariableW(L"PATH", orig, n) != n - 1) + { + exit(EXIT_FAILURE); + } + } + std::size_t len = (pathEnd - path) + (n == 0 ? 0 : MY_LENGTH(L";") + + (n - 1)) + 1; + //TODO: overflow + wchar_t * value = new wchar_t[len]; + _snwprintf( + value, len, L"%s%s%s", path, n == 0 ? L"" : L";", orig); + if (!SetEnvironmentVariableW(L"PATH", value)) { + exit(EXIT_FAILURE); + } + if (n != 0) { + delete [] orig; + } + delete [] value; + n = GetEnvironmentVariableW(L"PYTHONPATH", nullptr, 0); + if (n == 0) { + if (GetLastError() != ERROR_ENVVAR_NOT_FOUND) { + exit(EXIT_FAILURE); + } + orig = const_cast<wchar_t *>(L""); + } else { + orig = new wchar_t[n]; + if (GetEnvironmentVariableW(L"PYTHONPATH", orig, n) != n - 1) + { + exit(EXIT_FAILURE); + } + } + len = (pathEnd - path) + MY_LENGTH(L";") + (pythonpath2End - pythonpath2) + + MY_LENGTH(L";") + (pythonpath3End - pythonpath3) + + (n == 0 ? 0 : MY_LENGTH(L";") + (n - 1)) + 1; //TODO: overflow + value = new wchar_t[len]; + _snwprintf( + value, len, L"%s;%s;%s%s%s", path, pythonpath2, pythonpath3, + n == 0 ? L"" : L";", orig); + if (!SetEnvironmentVariableW(L"PYTHONPATH", value)) { + exit(EXIT_FAILURE); + } + if (n != 0) { + delete [] orig; + } + delete [] value; + if (!SetEnvironmentVariableW(L"PYTHONHOME", pythonhome)) { + exit(EXIT_FAILURE); + } + n = GetEnvironmentVariableW(L"URE_BOOTSTRAP", nullptr, 0); + if (n == 0) { + if (GetLastError() != ERROR_ENVVAR_NOT_FOUND || + !SetEnvironmentVariableW(L"URE_BOOTSTRAP", bootstrap)) + { + exit(EXIT_FAILURE); + } + } + STARTUPINFOW startinfo; + ZeroMemory(&startinfo, sizeof (STARTUPINFOW)); + startinfo.cb = sizeof (STARTUPINFOW); + PROCESS_INFORMATION procinfo; + if (!CreateProcessW( + pythonexe, cl, nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, nullptr, + nullptr, &startinfo, &procinfo)) { + exit(EXIT_FAILURE); + } + WaitForSingleObject(procinfo.hProcess,INFINITE); + DWORD exitStatus; + GetExitCodeProcess(procinfo.hProcess,&exitStatus); + exit(exitStatus); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/zipcore/python.sh b/pyuno/zipcore/python.sh new file mode 100755 index 0000000000..bfe6785bd6 --- /dev/null +++ b/pyuno/zipcore/python.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you 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 . +# + +# resolve installation directory +sd_cwd="`pwd`" +if [ -h "$0" ] ; then + sd_basename=`basename "$0"` + sd_script=`ls -l "$0" | sed "s/.*${sd_basename} -> //g"` + cd "`dirname "$0"`" + cd "`dirname "$sd_script"`" +else + cd "`dirname "$0"`" +fi +sd_prog=`pwd` +cd "$sd_cwd" + +# Set PATH so that crash_report is found: +PATH=$sd_prog${PATH+:$PATH} +export PATH + +# Set UNO_PATH so that "officehelper.bootstrap()" can find soffice executable: +: ${UNO_PATH=$sd_prog} +export UNO_PATH + |