summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew J. Francis <mjay.francis@gmail.com>2015-06-01 18:34:04 +0800
committerMatthew Francis <mjay.francis@gmail.com>2015-06-26 09:41:11 +0000
commitaf8143bc40cf2cfbc12e77c9bb7de01b655f7b30 (patch)
tree536e7c90190e960c9b9a6efeec5d38b359134a55
parenteab89b7f024a8c86decdcb3362c40c40a7df37df (diff)
Make PyUNO provide more Pythonic behaviour
- Simplifies working with UNO objects by giving the behaviour of Python lists, dicts and iterators to objects which implement UNO container interfaces - Applies a custom behaviour to allow objects which implement com::sun::star::table::XCellRange to yield cells and cell ranges by subscript - When UNO container objects are addressed in the new style, eliminates the requirement to manually construct Any objects for contained elements which are typed sequences - Allows lists and iterators to be passed wherever a UNO method accepts a sequence - Relaxes the requirements for initialising UNO structs to allow some members to be skipped when all initialisers are passed by name 1. Collection interfaces ======================== Objects which implement core UNO collection interfaces are made to behave in a way that is more natural for Python code. com::sun::star::container::XIndexAccess com::sun::star::container::XIndexReplace com::sun::star::container::XIndexContainer - Objects provide Python list access semantics num = len(obj) # Number of elements val = obj[0] # Access by index val1,val2 = obj[2:4] # Access by slice val1,val2 = obj[0:3:2] # Access by extended slice if val in obj: ... # Test value presence for val in obj: ... # Implicit iterator (values) itr = iter(obj) # Named iterator (values) obj[0] = val # Replace by index obj[2:4] = val1,val2 # Replace by slice obj[0:3:2] = val1,val2 # Replace by extended 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) del obj[0] # Delete by index del obj[2:4] # Delete by slice com::sun::star::container::XNameAccess com::sun::star::container::XNameReplace com::sun::star::container::XNameContainer - Objects provide Python dict access semantics num = len(obj) # Number of keys val = obj[key] # Access by key if key in obj: ... # Test key presence for key in obj: ... # Implicit iterator (keys) itr = iter(obj) # Named iterator (keys) obj[key] = val # Replace by key obj[key] = val # Insert by key del obj[key] # Delete by key com::sun::star::container::XEnumerationAccess - Objects provide Python iterable semantics for val in obj: ... # Implicit iterator itr = iter(obj) # Named iterator com::sun::star::container::XEnumeration - Objects provide Python iterator semantics for val in itr: ... # Iteration of named iterator if val in itr: ... # Test value presence Objects which implement both XIndex* and XName* are supported, and respond to both integer and string keys. However, iterating over such an object will return the keys (like a Python dict) rather than the values (like a Python list). 2. Cell ranges ============== A custom behaviour is applied to objects which implement com::sun::star::table::XCellRange to allow their cells and cell ranges to be addressed by subscript, in the style of a Python list or dict (read-only). This is applicable to Calc spreadsheet sheets, Writer text tables and cell ranges created upon these. cell = cellrange[0,0] # Access cell by indices rng = cellrange[0,1:2] # Access cell range by index,slice rng = cellrange[1:2,0] # Access cell range by slice,index rng = cellrange[0:1,2:3] # Access cell range by slices rng = cellrange['A1:B2'] # Access cell range by descriptor rng = cellrange['Name'] # Access cell range by name Note that the indices used are in Python/C order, and differ from the arguments to methods provided by XCellRange. - The statement cellrange[r,c], which returns the cell from row r and column c, is equivalent to calling XCellRange::getCellByPosition(c,r) - The statement cellrange[t:b,l:r], which returns a cell range covering rows t to b(non-inclusive) and columns l to r(non- inclusive), is equivalent to calling XCellRange::getCellRangeByPosition(l,t,r-1,b-1). In contrast to the handling of objects implementing XIndex*, extended slice syntax is not supported. Negative indices (from-end addresses) are supported only for objects which also implement com::sun::star::table::XColumnRowRange (currently Calc spreadsheet sheets and cell ranges created upon these). For such objects, the following syntax is also available: rng = cellrange[0] # Access cell range by row index rng = cellrange[0,:] # Access cell range by row index rng = cellrange[:,0] # Access cell range by column index 3. Elimination of explicit Any ============================== PyUNO has not previously been able to cope with certain method arguments which are typed as Any but require a sequence of specific type to be passed. This is a particular issue for container interfaces such as XIndexContainer and XNameContainer. The existing solution to dealing with such methods is to use a special method to pass an explicitly typed Any, giving code such as: index = doc.createInstance("com.sun.star.text.ContentIndex"); ... uno.invoke( index.LevelParagraphStyles , "replaceByIndex", (0, uno.Any("[]string", ('Caption',))) ) The new Pythonic container access is able to correctly infer the expected type of the sequences required by these arguments. In the new style, the above call to .replaceByIndex() can instead be written: index.LevelParagraphStyles[0] = ('Caption',) 4. List and iterator arguments ============================== Wherever a UNO API expects a sequence, a Python list or iterator can now be passed. This enables the use of list comprehensions and generator expressions for method calls and property assignments. Example: tbl = doc.createInstance('com.sun.star.text.TextTable') tbl.initialize(10,10) # ... insert table ... # Assign numbers 0..99 to the cells using a generator expression tbl.Data = ((y for y in range(10*x,10*x + 10)) for x in range(10)) 5. Tolerant struct initialisation ================================= Previously, a UNO struct could be created fully uninitialised, or by passing a combination of positional and/or named arguments to its constructor. However, if any arguments were passed, all members were required to be initialised or an exception was thrown. This requirement is relaxed such that when all arguments passed to a struct constructor are by name, some may be omitted. The existing requirement that all members must be explicitly initialised when some constructor arguments are unnamed (positional) is not affected. Example: from com.sun.star.beans import PropertyValue prop = PropertyValue(Name='foo', Value='bar') Change-Id: Id29bff10a18099b1a00af1abee1a6c1bc58b3978 Reviewed-on: https://gerrit.libreoffice.org/16272 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Matthew Francis <mjay.francis@gmail.com>
-rw-r--r--pyuno/Library_pyuno.mk1
-rw-r--r--pyuno/Module_pyuno.mk1
-rw-r--r--pyuno/PythonTest_pytests.mk1
-rw-r--r--pyuno/PythonTest_pyuno_pytests_testcollections.mk26
-rw-r--r--pyuno/inc/pyuno/pyuno.hxx6
-rw-r--r--pyuno/qa/pytests/testcollections_XCellRange.py365
-rw-r--r--pyuno/qa/pytests/testcollections_XEnumeration.py111
-rw-r--r--pyuno/qa/pytests/testcollections_XEnumerationAccess.py131
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexAccess.py299
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexContainer.py198
-rw-r--r--pyuno/qa/pytests/testcollections_XIndexReplace.py219
-rw-r--r--pyuno/qa/pytests/testcollections_XNameAccess.py182
-rw-r--r--pyuno/qa/pytests/testcollections_XNameContainer.py116
-rw-r--r--pyuno/qa/pytests/testcollections_XNameReplace.py73
-rw-r--r--pyuno/qa/pytests/testcollections_base.py58
-rw-r--r--pyuno/qa/pytests/testcollections_misc.py75
-rw-r--r--pyuno/qa/pytests/testcollections_mixednameindex.py48
-rw-r--r--pyuno/source/module/pyuno.cxx1039
-rw-r--r--pyuno/source/module/pyuno_impl.hxx49
-rw-r--r--pyuno/source/module/pyuno_iterator.cxx312
-rw-r--r--pyuno/source/module/pyuno_module.cxx24
-rw-r--r--pyuno/source/module/pyuno_runtime.cxx46
22 files changed, 3356 insertions, 24 deletions
diff --git a/pyuno/Library_pyuno.mk b/pyuno/Library_pyuno.mk
index 013765dc3c84..3bf08ccf3715 100644
--- a/pyuno/Library_pyuno.mk
+++ b/pyuno/Library_pyuno.mk
@@ -45,6 +45,7 @@ $(eval $(call gb_Library_add_exception_objects,pyuno,\
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/Module_pyuno.mk b/pyuno/Module_pyuno.mk
index ac09d980090c..4179a7318add 100644
--- a/pyuno/Module_pyuno.mk
+++ b/pyuno/Module_pyuno.mk
@@ -71,6 +71,7 @@ endif
ifneq (,$(filter PythonTest_pytests,$(MAKECMDGOALS)))
$(eval $(call gb_Module_add_targets,pyuno, \
PythonTest_pytests \
+ PythonTest_pyuno_pytests_testcollections \
PythonTest_pyuno_pytests_insertremovecells \
))
endif
diff --git a/pyuno/PythonTest_pytests.mk b/pyuno/PythonTest_pytests.mk
index 177195fb6d85..71255b638e5a 100644
--- a/pyuno/PythonTest_pytests.mk
+++ b/pyuno/PythonTest_pytests.mk
@@ -24,6 +24,7 @@
$(eval $(call gb_PythonTest_PythonTest,pytests))
$(call gb_PythonTest_get_target,pytests) : \
+ $(call gb_PythonTest_get_target,pyuno_pytests_testcollections) \
$(call gb_PythonTest_get_target,pyuno_pytests_insertremovecells) \
$(call gb_PythonTest_get_target,pyuno_pytests_ssl) \
diff --git a/pyuno/PythonTest_pyuno_pytests_testcollections.mk b/pyuno/PythonTest_pyuno_pytests_testcollections.mk
new file mode 100644
index 000000000000..ba8fe2e8cb7f
--- /dev/null
+++ b/pyuno/PythonTest_pyuno_pytests_testcollections.mk
@@ -0,0 +1,26 @@
+# -*- 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 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/pyuno/inc/pyuno/pyuno.hxx b/pyuno/inc/pyuno/pyuno.hxx
index c83beb99957f..825fa93c455f 100644
--- a/pyuno/inc/pyuno/pyuno.hxx
+++ b/pyuno/inc/pyuno/pyuno.hxx
@@ -171,6 +171,12 @@ enum ConversionMode { ACCEPT_UNO_ANY, REJECT_UNO_ANY };
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, com::sun::star::uno::Any & ) const;
public:
~Runtime( );
diff --git a/pyuno/qa/pytests/testcollections_XCellRange.py b/pyuno/qa/pytests/testcollections_XCellRange.py
new file mode 100644
index 000000000000..ad8819b0b869
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XCellRange.py
@@ -0,0 +1,365 @@
+#!/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
+from com.sun.star.table import CellAddress
+
+
+# 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.getByIndex(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)
+
+ # 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()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(0)
+
+ # When
+ cell = tbl[0,0]
+
+ # Then
+ self.assertEqual('A1', cell.CellName)
+
+ # 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.getByIndex(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)
+
+ # 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()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(0)
+
+ # When
+ cell = tbl[3,7]
+
+ # Then
+ self.assertEqual('H4', cell.CellName)
+
+ # 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.getByIndex(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)
+
+ # 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()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(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)
+
+ # 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.getByIndex(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)
+
+ # 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()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(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)
+
+ # 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.getByIndex(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)
+
+ # 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.getByIndex(0)
+
+ # When / Then
+ with self.assertRaises(KeyError):
+ rng = sht[1:1,3:5]
+ with self.assertRaises(KeyError):
+ rng = sht[1:3,3:3]
+
+ # 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()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(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)
+
+
+ # 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.getByIndex(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)
+
+ # Tests syntax:
+ # rng = cellrange['A1:B2'] # Access cell range by descriptor
+ # For:
+ # Table
+ def test_XCellRange_Table_Range_Descriptor(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ textTable = doc.createInstance('com.sun.star.text.TextTable')
+ textTable.initialize(10,10)
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+ tbl = doc.TextTables.getByIndex(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)
+
+ # 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.getByIndex(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)
+
+ # 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.getByIndex(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(1023, rng.RangeAddress.EndColumn)
+
+ # 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.getByIndex(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(1023, rng.RangeAddress.EndColumn)
+
+ # 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.getByIndex(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)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XEnumeration.py b/pyuno/qa/pytests/testcollections_XEnumeration.py
new file mode 100644
index 000000000000..9be1a60b9985
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XEnumeration.py
@@ -0,0 +1,111 @@
+#!/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
+
+
+# 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))
+
+ # 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)
+
+ # 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)
+
+ # 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)
+
+ # 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)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XEnumerationAccess.py b/pyuno/qa/pytests/testcollections_XEnumerationAccess.py
new file mode 100644
index 000000000000..1ad7a08f31fa
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XEnumerationAccess.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
+import uno
+
+from testcollections_base import CollectionsTestBase
+from com.sun.star.beans import PropertyValue
+
+
+# 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))
+
+ # 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)
+
+ # 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)
+
+ # 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)
+
+ # 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)
+
+ # 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)
+
+ # 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
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XIndexAccess.py b/pyuno/qa/pytests/testcollections_XIndexAccess.py
new file mode 100644
index 000000000000..753373b6b933
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XIndexAccess.py
@@ -0,0 +1,299 @@
+#!/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 inspect import isclass
+
+from testcollections_base import CollectionsTestBase
+from com.sun.star.beans import PropertyValue
+
+
+# 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 = doc.createInstance("com.sun.star.text.Footnote")
+ 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)
+
+ # 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 = doc.createInstance("com.sun.star.text.Footnote")
+ doc.Text.insertTextContent(cursor, footnote, 0)
+
+ # When
+ count = len(doc.Footnotes)
+
+ # Then
+ self.assertEqual(1, count)
+
+ # 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)
+
+ 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)
+
+ # Tests syntax:
+ # val1,val2 = obj[2:4] # Access by slice
+ def test_XIndexAccess_ReadSlice(self):
+ doc = self.createBlankTextDocument()
+ testMax = 4
+ for i in range(testMax):
+ t = tuple(range(i))
+ for j in [x for x in range(-testMax-2,testMax+3)] + [None]:
+ for k in [x for x in range(-testMax-2,testMax+3)] + [None]:
+ key = slice(j,k)
+ expected = t[key]
+ self.readValuesTestFixture(doc, i, key, expected)
+
+ # Tests syntax:
+ # val1,val2 = obj[0:3:2] # Access by extended slice
+ def test_XIndexAccess_ReadExtendedSlice(self):
+ doc = self.createBlankTextDocument()
+ testMax = 4
+ for i in range(testMax):
+ t = tuple(range(i))
+ for j in [x for x in range(-testMax-2,testMax+3)] + [None]:
+ for k in [x for x in range(-testMax-2,testMax+3)] + [None]:
+ for l in [-2,-1,2]:
+ key = slice(j,k,l)
+ expected = t[key]
+ self.readValuesTestFixture(doc, i, key, expected)
+
+ # 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 = doc.createInstance("com.sun.star.text.Footnote")
+ footnote.setLabel('foo')
+ doc.Text.insertTextContent(cursor, footnote, 0)
+ footnote = doc.Footnotes.getByIndex(0);
+
+ # When
+ present = footnote in doc.Footnotes
+
+ # Then
+ self.assertTrue(present)
+
+ # 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)
+
+ # 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)
+
+ # 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
+
+ # Tests syntax:
+ # for val in obj: ... # Implicit iterator (values)
+ # For:
+ # 0 elements
+ def test_XIndexAccess_ForIn_0(self):
+ # Given
+ doc = self.createBlankTextDocument()
+
+ # When
+ readFootnotes = []
+ for f in doc.Footnotes:
+ readFootnotes.append(f)
+
+ # Then
+ self.assertEqual(0, len(readFootnotes))
+
+ # 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 = doc.createInstance("com.sun.star.text.Footnote")
+ footnote.setLabel('foo')
+ doc.Text.insertTextContent(cursor, footnote, 0)
+
+ # When
+ readFootnotes = []
+ for f in doc.Footnotes:
+ readFootnotes.append(f)
+
+ # Then
+ self.assertEqual(1, len(readFootnotes))
+ self.assertEqual('foo', readFootnotes[0].Label)
+
+ # 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 = doc.createInstance("com.sun.star.text.Footnote")
+ footnote2 = doc.createInstance("com.sun.star.text.Footnote")
+ footnote1.setLabel('foo')
+ footnote2.setLabel('bar')
+ doc.Text.insertTextContent(cursor, footnote1, 0)
+ doc.Text.insertTextContent(cursor, footnote2, 0)
+
+ # When
+ readFootnotes = []
+ for f in doc.Footnotes:
+ readFootnotes.append(f)
+
+ # Then
+ self.assertEqual(2, len(readFootnotes))
+ self.assertEqual('foo', readFootnotes[0].Label)
+ self.assertEqual('bar', readFootnotes[1].Label)
+
+ # 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 = doc.createInstance("com.sun.star.text.Footnote")
+ 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)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XIndexContainer.py b/pyuno/qa/pytests/testcollections_XIndexContainer.py
new file mode 100644
index 000000000000..aad9b848b841
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XIndexContainer.py
@@ -0,0 +1,198 @@
+#!/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
+
+
+# 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
+ propertyValues = self.generateTestPropertyValues(count)
+ if type(values) is list:
+ toAssign = self.generateTestTuple(values)
+ else:
+ toAssign = values
+ if not (isinstance(expected, Exception)):
+ toCompare = self.generateTestTuple(expected)
+
+ # When
+ captured = None
+ try:
+ propertyValues[key] = toAssign
+ 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), propertyValues.getCount())
+ for i in range(propertyValues.getCount()):
+ self.assertEqual(toCompare[i][0].Name, propertyValues.getByIndex(i)[0].Name)
+
+ def deleteValuesTestFixture(self, count, key, expected):
+ # Given
+ propertyValues = self.generateTestPropertyValues(count)
+ if not (isinstance(expected, Exception)):
+ toCompare = self.generateTestTuple(expected)
+
+ # When
+ captured = None
+ try:
+ del propertyValues[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), propertyValues.getCount())
+ for i in range(propertyValues.getCount()):
+ self.assertEqual(toCompare[i][0].Name, propertyValues.getByIndex(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):
+ baseMax = 5
+ assignMax = 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)
+ for l in range(assignMax):
+ 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 = doc.createInstance("com.sun.star.form.component.DataForm")
+ form.Name = 'foo'
+
+ # When
+ doc.DrawPage.Forms[0:0] = (form,)
+
+ # Then
+ self.assertEqual('foo', doc.DrawPage.Forms[0].Name)
+
+ # Tests syntax:
+ # obj[0:3:2] = val1,val2 # Replace by extended slice
+ # For:
+ # Cases requiring sequence type coercion
+ def test_XIndexContainer_AssignExtendedSlice(self):
+ baseMax = 5
+ assignMax = 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]:
+ for l in [-2,-1,1,2]:
+ key = slice(j,k,l)
+ for m in range(assignMax):
+ 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):
+ baseMax = 5
+ for i in range(baseMax):
+ for j in [x for x in range(-baseMax-2,baseMax+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: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XIndexReplace.py b/pyuno/qa/pytests/testcollections_XIndexReplace.py
new file mode 100644
index 000000000000..b8e0da59326e
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XIndexReplace.py
@@ -0,0 +1,219 @@
+#!/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
+
+
+# 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 = doc.createInstance("com.sun.star.text.ContentIndex");
+ 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)
+ toAssign = self.generateTestTuple(values)
+ if not (isinstance(expected, Exception)):
+ toCompare = self.generateTestTuple(expected)
+
+ # When
+ captured = None
+ try:
+ index.LevelParagraphStyles[key] = toAssign
+ 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.getByIndex(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 = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When
+ index.LevelParagraphStyles[0] = ('Caption',)
+
+ # Then
+ self.assertEqual(('Caption',), index.LevelParagraphStyles[0])
+
+ # Tests syntax:
+ # obj[0] = val # Replace by index
+ # For:
+ # Invalid value (None)
+ def test_XIndexReplace_ReplaceIndex_Invalid_None(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ index = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = None
+
+ # Tests syntax:
+ # obj[0] = val # Replace by index
+ # For:
+ # Invalid value (String)
+ def test_XIndexReplace_ReplaceIndex_Invalid_String(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ index = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = 'foo'
+
+ # Tests syntax:
+ # obj[0] = val # Replace by index
+ # For:
+ # Invalid value (Float)
+ def test_XIndexReplace_ReplaceIndex_Invalid_Float(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ index = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = 12.34
+
+ # Tests syntax:
+ # obj[0] = val # Replace by index
+ # For:
+ # Invalid value (List)
+ def test_XIndexReplace_ReplaceIndex_Invalid_List(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ index = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = [0,1]
+
+ # Tests syntax:
+ # obj[0] = val # Replace by index
+ # For:
+ # Invalid value (Dict)
+ def test_XIndexReplace_ReplaceIndex_Invalid_Dict(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ index = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = {'a': 'b'}
+
+ # 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 = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0] = ('Caption', ())
+
+ # Tests syntax:
+ # obj[2:4] = val1,val2 # Replace by slice
+ # For:
+ # Cases requiring sequence type coercion
+ def test_XIndexReplace_ReplaceSlice(self):
+ assignMax = 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(assignMax):
+ 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)
+
+ # 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 = doc.createInstance("com.sun.star.text.ContentIndex");
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ index.LevelParagraphStyles[0:2] = (
+ ('Caption',),
+ 12.34
+ )
+
+ # Tests syntax:
+ # obj[0:3:2] = val1,val2 # Replace by extended slice
+ # For:
+ # Cases requiring sequence type coercion
+ def test_XIndexReplace_ReplaceExtendedSlice(self):
+ assignMax = 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(assignMax):
+ 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)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XNameAccess.py b/pyuno/qa/pytests/testcollections_XNameAccess.py
new file mode 100644
index 000000000000..f123641fc324
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XNameAccess.py
@@ -0,0 +1,182 @@
+#!/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
+
+
+# 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 = len (drw.Links)
+
+ # Then
+ self.assertEqual(2, length)
+
+ # Tests syntax:
+ # val = obj[key] # Access by key
+ # For:
+ # 1/2 elements
+ def test_XNameAccess_ReadKey(self):
+ # Given
+ drw = self.createBlankDrawing()
+ drw.DrawPages.getByIndex(0).Name = 'foo'
+
+ # When
+ link = drw.Links['foo']
+
+ # Then
+ self.assertEqual('foo', link.getName())
+
+ # 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['foo']
+
+ # 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]
+
+ # 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]
+
+ # 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)]
+
+ # 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]]
+
+ # 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'}]
+
+ # Tests syntax:
+ # if key in obj: ... # Test key presence
+ # For:
+ # 1/2 elements
+ def test_XNameAccess_In(self):
+ # Given
+ drw = self.createBlankDrawing()
+ drw.DrawPages.getByIndex(0).Name = 'foo'
+
+ # When
+ present = 'foo' in drw.Links
+
+ # Then
+ self.assertTrue(present)
+
+
+ # 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.getElementNames():
+ drw.Links.getByName(name).Name = 'foo' + str(i)
+ i += 1
+
+ # When
+ readLinks = []
+ for link in drw.Links:
+ readLinks.append(link)
+
+ # Then
+ self.assertEqual(['foo0','foo1'], readLinks)
+
+ # 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)
+
+ # Then
+ self.assertIsNotNone(next(itr))
+ self.assertIsNotNone(next(itr))
+ with self.assertRaises(StopIteration):
+ next(itr)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XNameContainer.py b/pyuno/qa/pytests/testcollections_XNameContainer.py
new file mode 100644
index 000000000000..80b60465882d
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XNameContainer.py
@@ -0,0 +1,116 @@
+#!/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
+
+
+# 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 = spr.createInstance("com.sun.star.sheet.SheetCellRanges")
+ newRange = spr.Sheets.getByIndex(0).getCellRangeByPosition( 1, 2, 1, 2 )
+
+ # When
+ ranges['foo'] = newRange
+
+ # Then
+ self.assertEqual(1, len(ranges.ElementNames))
+
+ # Tests syntax:
+ # obj[key] = val # Insert by key
+ # For:
+ # Invalid key
+ def test_XNameContainer_InsertName_Invalid(self):
+ # Given
+ spr = self.createBlankSpreadsheet()
+ ranges = spr.createInstance("com.sun.star.sheet.SheetCellRanges")
+ newRange = spr.Sheets.getByIndex(0).getCellRangeByPosition( 1, 2, 1, 2 )
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ ranges[12.34] = newRange
+
+ # Tests syntax:
+ # obj[key] = val # Replace by key
+ def test_XNameContainer_ReplaceName(self):
+ # Given
+ spr = self.createBlankSpreadsheet()
+ ranges = spr.createInstance("com.sun.star.sheet.SheetCellRanges")
+ newRange1 = spr.Sheets.getByIndex(0).getCellRangeByPosition( 1, 2, 1, 2 )
+ newRange2 = spr.Sheets.getByIndex(0).getCellRangeByPosition( 6, 6, 6, 6 )
+
+ # When
+ ranges['foo'] = newRange1
+ ranges['foo'] = newRange2
+
+ # Then
+ self.assertEqual(1, len(ranges.ElementNames))
+ readRange = ranges['foo']
+ self.assertEqual(6, readRange.CellAddress.Column)
+
+ # 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)
+
+ # 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']
+
+ # 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]
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_XNameReplace.py b/pyuno/qa/pytests/testcollections_XNameReplace.py
new file mode 100644
index 000000000000..622311433c41
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_XNameReplace.py
@@ -0,0 +1,73 @@
+#!/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
+
+
+# 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()
+ scriptName = 'macro://Standard.Module1.MySave()'
+ eventProperties = (PropertyValue(Name='Script', Value=scriptName),)
+
+ # When
+ doc.Events['OnSave'] = eventProperties
+
+ # Then
+ onSave = [p.Value for p in doc.Events['OnSave'] if p.Name == 'Script'][0]
+ self.assertEqual(scriptName, onSave)
+
+ # Tests syntax:
+ # obj[key] = val # Replace by key
+ # For:
+ # Invalid key
+ def test_XNameReplace_ReplaceName_Invalid(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ scriptName = 'macro://Standard.Module1.MySave()'
+ eventProperties = (PropertyValue(Name='Script', Value=scriptName),)
+
+ # When / Then
+ with self.assertRaises(KeyError):
+ doc.Events['qqqqq'] = eventProperties
+
+ # Tests syntax:
+ # obj[key] = val # Replace by key
+ # For:
+ # Invalid key type
+ def test_XNameReplace_ReplaceName_Invalid(self):
+ # Given
+ doc = self.createBlankTextDocument()
+ scriptName = 'macro://Standard.Module1.MySave()'
+ eventProperties = (PropertyValue(Name='Script', Value=scriptName),)
+
+ # When / Then
+ with self.assertRaises(TypeError):
+ doc.Events[12.34] = eventProperties
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_base.py b/pyuno/qa/pytests/testcollections_base.py
new file mode 100644
index 000000000000..2efb6f4a7578
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_base.py
@@ -0,0 +1,58 @@
+#!/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 org.libreoffice.unotest import pyuno
+from com.sun.star.beans import PropertyValue
+
+class CollectionsTestBase(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.context = pyuno.getComponentContext()
+ pyuno.private_initTestEnvironment()
+
+ def setUp(self):
+ self._components = []
+
+ def tearDown(self):
+ for component in self._components:
+ try:
+ component.close(True)
+ except Exception:
+ pass
+
+ def createHiddenWindow(self, url):
+ serviceManager = self.context.ServiceManager
+ desktop = serviceManager.createInstanceWithContext('com.sun.star.frame.Desktop', self.context)
+ loadProps = (
+ PropertyValue(Name='Hidden', Value=True),
+ PropertyValue(Name='ReadOnly', Value=False)
+ )
+ component = desktop.loadComponentFromURL(url, '_blank', 0, loadProps)
+ 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: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_misc.py b/pyuno/qa/pytests/testcollections_misc.py
new file mode 100644
index 000000000000..c66f6f440598
--- /dev/null
+++ b/pyuno/qa/pytests/testcollections_misc.py
@@ -0,0 +1,75 @@
+#!/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
+
+
+# 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
+
+ # 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
+
+ # 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)
+
+ # 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]
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/qa/pytests/testcollections_mixednameindex.py b/pyuno/qa/pytests/testcollections_mixednameindex.py
new file mode 100644
index 000000000000..cf4c9e18e7ba
--- /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
+import uno
+
+from testcollections_base import CollectionsTestBase
+from com.sun.star.beans import PropertyValue
+
+
+# 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()
+ textTable = doc.createInstance("com.sun.star.text.TextTable")
+ textTable.initialize(2,2)
+ textTable.Name = 'foo'
+ cursor = doc.Text.createTextCursor()
+ doc.Text.insertTextContent(cursor, textTable, False)
+
+ # When
+ tableByName = doc.TextTables['foo']
+ tableByIndex = doc.TextTables[0]
+
+ # Then
+ self.assertEqual('foo', tableByName.Name)
+ self.assertEqual('foo', tableByIndex.Name)
+ self.assertEqual(tableByName,tableByIndex)
+
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab: \ No newline at end of file
diff --git a/pyuno/source/module/pyuno.cxx b/pyuno/source/module/pyuno.cxx
index 86220376e0cb..ce9983eb42f8 100644
--- a/pyuno/source/module/pyuno.cxx
+++ b/pyuno/source/module/pyuno.cxx
@@ -19,6 +19,7 @@
#include <sal/config.h>
+#include <algorithm>
#include <cassert>
#include "pyuno_impl.hxx"
@@ -28,10 +29,21 @@
#include <osl/thread.h>
+#include <typelib/typedescription.hxx>
+
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XTypeProvider.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XMaterialHolder.hpp>
+#include <com/sun/star/container/XElementAccess.hpp>
+#include <com/sun/star/container/XEnumeration.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>
using com::sun::star::uno::Sequence;
using com::sun::star::uno::Reference;
@@ -41,6 +53,7 @@ using com::sun::star::uno::makeAny;
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::uno::XComponentContext;
@@ -50,6 +63,15 @@ using com::sun::star::lang::XTypeProvider;
using com::sun::star::script::XTypeConverter;
using com::sun::star::script::XInvocation2;
using com::sun::star::beans::XMaterialHolder;
+using com::sun::star::container::XElementAccess;
+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
{
@@ -293,6 +315,57 @@ OUString val2str( const void * pVal, typelib_TypeDescriptionReference * pTypeRef
return buf.makeStringAndClear();
}
+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;
+}
+
+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( (PySliceObject_t*)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 = (sal_Int32)nStart_ssize;
+ *nStop = (sal_Int32)nStop_ssize;
+ *nStep = (sal_Int32)nStep_ssize;
+ *nSliceLength = (sal_Int32)nSliceLength_ssize;
+ return 0;
+}
+
+bool lcl_hasInterfaceByName( Any &object, OUString interfaceName )
+{
+ Reference< XInterface > xInterface( object, UNO_QUERY );
+ TypeDescription typeDesc( interfaceName );
+ Any aInterface = xInterface->queryInterface( typeDesc.get()->pWeakRef );
+
+ return aInterface.hasValue();
+}
PyObject *PyUNO_repr( PyObject * self )
{
@@ -451,6 +524,880 @@ PyObject* PyUNO_dir (PyObject* self)
return member_list;
}
+sal_Int32 lcl_detach_getLength( PyUNO *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->wrappedObject, 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->wrappedObject, UNO_QUERY );
+ if ( xNameAccess.is() )
+ {
+ return xNameAccess->getElementNames().getLength();
+ }
+
+ return -1;
+}
+
+int PyUNO_bool( PyObject* self )
+{
+ PyUNO* me = reinterpret_cast<PyUNO*>(self);
+
+ try
+ {
+ int nLen = lcl_detach_getLength( me );
+ if (nLen >= 0)
+ return !!nLen;
+
+ // Anything which doesn't have members is a scalar object and therefore true
+ return 1;
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return -1;
+}
+
+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 ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return -1;
+}
+
+void lcl_getRowsColumns( PyUNO* me, sal_Int32& nRows, sal_Int32& nColumns )
+{
+ Sequence<short> aOutParamIndex;
+ Sequence<Any> aOutParam;
+ Sequence<Any> aParams;
+ Any aRet;
+
+ 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();
+}
+
+PyRef lcl_indexToSlice( PyRef rIndex )
+{
+ Py_ssize_t nIndex = PyNumber_AsSsize_t( rIndex.get(), PyExc_IndexError );
+ if (nIndex == -1 && PyErr_Occurred())
+ return NULL;
+ 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;
+}
+
+PyObject* lcl_getitem_XCellRange( PyUNO* me, PyObject* pKey )
+{
+ Runtime runtime;
+
+ Sequence<short> aOutParamIndex;
+ Sequence<Any> aOutParam;
+ Sequence<Any> aParams;
+ Any aRet;
+
+ // Single string key is sugar for getCellRangeByName()
+ if ( PyStr_Check( pKey ) ) {
+
+ aParams.realloc (1);
+ aParams[0] <<= 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( NULL, NULL, NULL );
+ }
+ 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 NULL;
+ }
+
+ // 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 NULL;
+
+ aParams.realloc( 2 );
+ aParams[0] <<= nKey1_s;
+ aParams[1] <<= 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 NULL;
+
+ if ( nSliceLength0 <= 0 || nSliceLength1 <= 0 )
+ {
+ PyErr_SetString( PyExc_KeyError, "invalid number of rows or columns" );
+ return NULL;
+ }
+
+ if ( nStep0 == 1 && nStep1 == 1 )
+ {
+ aParams.realloc (4);
+ aParams[0] <<= nStart1;
+ aParams[1] <<= nStart0;
+ aParams[2] <<= nStop1 - 1;
+ aParams[3] <<= 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 NULL;
+ }
+
+ PyErr_SetString( PyExc_KeyError, "invalid subscript" );
+ return NULL;
+}
+
+PyObject* lcl_getitem_index( PyUNO *me, PyObject *pKey, Runtime& runtime )
+{
+ Any aRet;
+ sal_Int32 nIndex;
+
+ nIndex = lcl_PyNumber_AsSal_Int32( pKey );
+ if (nIndex == -1 && PyErr_Occurred())
+ return NULL;
+
+ {
+ PyThreadDetach antiguard;
+
+ Reference< XIndexAccess > xIndexAccess( me->members->wrappedObject, 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 NULL;
+}
+
+PyObject* lcl_getitem_slice( PyUNO *me, PyObject *pKey )
+{
+ Runtime runtime;
+
+ Reference< XIndexAccess > xIndexAccess;
+ sal_Int32 nLen = 0;
+
+ {
+ PyThreadDetach antiguard;
+
+ xIndexAccess.set( me->members->wrappedObject, UNO_QUERY );
+ if ( xIndexAccess.is() )
+ nLen = xIndexAccess->getCount();
+ }
+
+ if ( xIndexAccess.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 NULL;
+
+ 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();
+ }
+
+ return NULL;
+}
+
+PyObject* lcl_getitem_string( PyUNO *me, PyObject *pKey, Runtime& runtime )
+{
+ OUString sKey = pyString2ustring( pKey );
+ Any aRet;
+
+ {
+ PyThreadDetach antiguard;
+
+ Reference< XNameAccess > xNameAccess( me->members->wrappedObject, UNO_QUERY );
+ if ( xNameAccess.is() )
+ {
+ aRet = xNameAccess->getByName( sKey );
+ }
+ }
+ if ( aRet.hasValue() )
+ {
+ PyRef rRet = runtime.any2PyObject( aRet );
+ return rRet.getAcquired();
+ }
+
+ return NULL;
+}
+
+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 != NULL || PyErr_Occurred() )
+ return pRet;
+ }
+
+ // XIndexAccess access by slice
+ if ( PySlice_Check( pKey ) )
+ {
+ PyObject* pRet = lcl_getitem_slice( me, pKey );
+ if ( pRet != NULL || PyErr_Occurred() )
+ return pRet;
+ }
+
+ // XNameAccess access by key
+ if ( PyStr_Check( pKey ) )
+ {
+ PyObject* pRet = lcl_getitem_string( me, pKey, runtime );
+ if ( pRet != NULL )
+ 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->wrappedObject, UNO_QUERY );
+ Reference< XNameAccess > xNameAccess( me->members->wrappedObject, UNO_QUERY );
+ if ( xIndexAccess.is() || xNameAccess.is() )
+ {
+ PyErr_SetString( PyExc_TypeError, "subscription with invalid type" );
+ return NULL;
+ }
+
+ PyErr_SetString( PyExc_TypeError, "object is not subscriptable" );
+ }
+ catch( const ::com::sun::star::lang::IndexOutOfBoundsException )
+ {
+ PyErr_SetString( PyExc_IndexError, "index out of range" );
+ }
+ catch( const ::com::sun::star::container::NoSuchElementException )
+ {
+ PyErr_SetString( PyExc_KeyError, "key not found" );
+ }
+ catch( const com::sun::star::script::CannotConvertException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const com::sun::star::lang::IllegalArgumentException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::lang::WrappedTargetException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return NULL;
+}
+
+int lcl_setitem_index( PyUNO *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 != NULL )
+ {
+ isTuple = PyTuple_Check( pValue );
+
+ try
+ {
+ aValue <<= runtime.pyObject2Any( pValue );
+ }
+ catch ( const ::com::sun::star::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 ::com::sun::star::script::CannotConvertException();
+ }
+ }
+
+ {
+ PyThreadDetach antiguard;
+
+ xIndexContainer.set( me->members->wrappedObject, UNO_QUERY );
+ if ( xIndexContainer.is() )
+ xIndexReplace.set( xIndexContainer, UNO_QUERY );
+ else
+ xIndexReplace.set( me->members->wrappedObject, UNO_QUERY );
+
+ if ( xIndexReplace.is() && nIndex < 0 )
+ nIndex += xIndexReplace->getCount();
+
+ // XIndexReplace replace by index
+ if ( (pValue != NULL) && 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 == NULL) && xIndexContainer.is() )
+ {
+ xIndexContainer->removeByIndex( nIndex );
+ return 0;
+ }
+ }
+
+ PyErr_SetString( PyExc_TypeError, "cannot assign to object" );
+ return 1;
+}
+
+int lcl_setitem_slice( PyUNO *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->wrappedObject, UNO_QUERY );
+ if ( xIndexContainer.is() )
+ xIndexReplace.set( xIndexContainer, UNO_QUERY );
+ else
+ xIndexReplace.set( me->members->wrappedObject, 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 == NULL )
+ {
+ 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 = (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 ::com::sun::star::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 ::com::sun::star::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;
+}
+
+int lcl_setitem_string( PyUNO *me, PyObject *pKey, PyObject *pValue )
+{
+ Runtime runtime;
+
+ OUString sKey = pyString2ustring( pKey );
+ bool isTuple = false;
+
+ Any aValue;
+ if ( pValue != NULL)
+ {
+ isTuple = PyTuple_Check( pValue );
+ try
+ {
+ aValue <<= runtime.pyObject2Any( pValue );
+ }
+ catch( const ::com::sun::star::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 ::com::sun::star::script::CannotConvertException();
+ }
+ }
+
+ {
+ PyThreadDetach antiguard;
+
+ Reference< XNameContainer > xNameContainer( me->members->wrappedObject, UNO_QUERY );
+ Reference< XNameReplace > xNameReplace;
+ if ( xNameContainer.is() )
+ xNameReplace.set( xNameContainer, UNO_QUERY );
+ else
+ xNameReplace.set( me->members->wrappedObject, 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( com::sun::star::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;
+}
+
+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 ( PyStr_Check( pKey ) )
+ {
+ return lcl_setitem_string( me, pKey, pValue );
+ }
+
+ PyErr_SetString( PyExc_TypeError, "list index has invalid type" );
+ }
+ catch( const ::com::sun::star::lang::IndexOutOfBoundsException )
+ {
+ PyErr_SetString( PyExc_IndexError, "list index out of range" );
+ }
+ catch( const ::com::sun::star::container::NoSuchElementException )
+ {
+ PyErr_SetString( PyExc_KeyError, "key not found" );
+ }
+ catch( const ::com::sun::star::lang::IllegalArgumentException )
+ {
+ PyErr_SetString( PyExc_TypeError, "value has invalid type" );
+ }
+ catch( com::sun::star::script::CannotConvertException )
+ {
+ PyErr_SetString( PyExc_TypeError, "value has invalid type" );
+ }
+ catch( const ::com::sun::star::container::ElementExistException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const::com::sun::star::lang::WrappedTargetException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return 1;
+}
+
+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->wrappedObject, UNO_QUERY );
+ if ( xEnumerationAccess.is() )
+ xEnumeration = xEnumerationAccess->createEnumeration();
+ else
+ xEnumeration.set( me->members->wrappedObject, UNO_QUERY );
+
+ if ( !xEnumeration.is() )
+ xIndexAccess.set( me->members->wrappedObject, UNO_QUERY );
+
+ if ( !xIndexAccess.is() )
+ xNameAccess.set( me->members->wrappedObject, 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( com::sun::star::script::CannotConvertException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( com::sun::star::lang::IllegalArgumentException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return NULL;
+}
+
+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 ::com::sun::star::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 ::com::sun::star::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 ( PyStr_Check( pKey ) )
+ {
+ OUString sKey;
+ aValue >>= sKey;
+ Reference< XNameAccess > xNameAccess;
+
+ {
+ PyThreadDetach antiguard;
+
+ xNameAccess.set( me->members->wrappedObject, UNO_QUERY );
+ if ( xNameAccess.is() )
+ {
+ sal_Bool hasKey = xNameAccess->hasByName( sKey );
+ return hasKey == sal_True ? 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() )
+ {
+ PyObject* pItem;
+ while ( (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 com::sun::star::script::CannotConvertException )
+ {
+ PyErr_SetString( PyExc_TypeError, "invalid type passed as left argument to 'in'" );
+ }
+ catch( const ::com::sun::star::container::NoSuchElementException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::lang::IndexOutOfBoundsException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const com::sun::star::lang::IllegalArgumentException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::lang::WrappedTargetException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return -1;
+}
PyObject* PyUNO_getattr (PyObject* self, char* name)
{
@@ -644,17 +1591,83 @@ static PyObject* PyUNO_cmp( PyObject *self, PyObject *that, int op )
static PyMethodDef PyUNOMethods[] =
{
- {"__dir__", reinterpret_cast<PyCFunction>(PyUNO_dir), METH_NOARGS, NULL},
- {NULL, NULL, 0, NULL}
+ {"__dir__", reinterpret_cast<PyCFunction>(PyUNO_dir), METH_NOARGS, NULL},
+ {NULL, NULL, 0, NULL}
};
-
-/* Python 2 has a tp_flags value for rich comparisons. Python 3 does not (on by default) */
-#ifdef Py_TPFLAGS_HAVE_RICHCOMPARE
-#define TP_FLAGS (Py_TPFLAGS_HAVE_RICHCOMPARE)
-#else
-#define TP_FLAGS 0
+static PyNumberMethods PyUNONumberMethods[] =
+{
+ nullptr, /* nb_add */
+ nullptr, /* nb_subtract */
+ nullptr, /* nb_multiply */
+#if PY_MAJOR_VERSION < 3
+ nullptr, /* nb_divide */
#endif
+ 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 */
+#if PY_MAJOR_VERSION < 3
+ nullptr, /* nb_coerce */
+#endif
+ nullptr, /* nb_int */
+ nullptr, /* nb_reserved */
+ nullptr, /* nb_float */
+#if PY_MAJOR_VERSION < 3
+ nullptr, /* nb_oct */
+ nullptr, /* nb_hex */
+#endif
+ nullptr, /* nb_inplace_add */
+ nullptr, /* nb_inplace_subtract */
+ nullptr, /* nb_inplace_multiply */
+#if PY_MAJOR_VERSION < 3
+ nullptr, /* nb_inplace_divide */
+#endif
+ 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 */
+};
+
+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 =
{
@@ -668,22 +1681,22 @@ static PyTypeObject PyUNOType =
PyUNO_setattr,
/* this type does not exist in Python 3: (cmpfunc) */ 0,
PyUNO_repr,
- 0,
- 0,
- 0,
+ PyUNONumberMethods,
+ PyUNOSequenceMethods,
+ PyUNOMappingMethods,
nullptr,
nullptr,
PyUNO_str,
nullptr,
nullptr,
NULL,
- TP_FLAGS,
+ Py_TPFLAGS_HAVE_ITER | Py_TPFLAGS_HAVE_RICHCOMPARE | Py_TPFLAGS_HAVE_SEQUENCE_IN,
NULL,
nullptr,
nullptr,
PyUNO_cmp,
0,
- nullptr,
+ PyUNO_iter,
nullptr,
PyUNOMethods,
NULL,
diff --git a/pyuno/source/module/pyuno_impl.hxx b/pyuno/source/module/pyuno_impl.hxx
index e066b7327b12..f7824ba50566 100644
--- a/pyuno/source/module/pyuno_impl.hxx
+++ b/pyuno/source/module/pyuno_impl.hxx
@@ -31,6 +31,17 @@
#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
#endif
+//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/pyuno.hxx>
#include <unordered_map>
@@ -43,6 +54,8 @@
#include <com/sun/star/reflection/XIdlReflection.hpp>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/lang/XUnoTunnel.hpp>
@@ -130,6 +143,13 @@ inline PyObject* PyStrBytes_FromStringAndSize(const char *string, Py_ssize_t len
}
#endif /* PY_MAJOR_VERSION >= 3 */
+// Type of argument to PySlice_GetIndicesEx() changed in Python 3.2
+#if PY_VERSION_HEX >= 0x030200f0
+typedef PyObject PySliceObject_t;
+#else
+typedef PySliceObject PySliceObject_t;
+#endif
+
namespace pyuno
{
@@ -212,6 +232,35 @@ typedef struct
PyUNOInternals* members;
} PyUNO;
+PyObject* PyUNO_iterator_new (
+ const com::sun::star::uno::Reference<com::sun::star::container::XEnumeration> xEnumeration);
+
+typedef struct
+{
+ com::sun::star::uno::Reference <com::sun::star::container::XEnumeration> xEnumeration;
+} PyUNO_iterator_Internals;
+
+typedef struct
+{
+ PyObject_HEAD
+ PyUNO_iterator_Internals* members;
+} PyUNO_iterator;
+
+PyObject* PyUNO_list_iterator_new (
+ const com::sun::star::uno::Reference<com::sun::star::container::XIndexAccess> &xIndexAccess);
+
+typedef struct
+{
+ com::sun::star::uno::Reference <com::sun::star::container::XIndexAccess> xIndexAccess;
+ int index;
+} PyUNO_list_iterator_Internals;
+
+typedef struct
+{
+ PyObject_HEAD
+ PyUNO_list_iterator_Internals* members;
+} PyUNO_list_iterator;
+
PyRef ustring2PyUnicode( const OUString &source );
PyRef ustring2PyString( const OUString & source );
OUString pyString2ustring( PyObject *str );
diff --git a/pyuno/source/module/pyuno_iterator.cxx b/pyuno/source/module/pyuno_iterator.cxx
new file mode 100644
index 000000000000..f9dd8d0904d6
--- /dev/null
+++ b/pyuno/source/module/pyuno_iterator.cxx
@@ -0,0 +1,312 @@
+/* -*- 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 <cassert>
+
+#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>
+
+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;
+using com::sun::star::uno::UNO_QUERY;
+
+
+namespace pyuno
+{
+
+void PyUNO_iterator_del( PyObject* self )
+{
+ PyUNO_iterator* me = reinterpret_cast<PyUNO_iterator*>(self);
+
+ {
+ PyThreadDetach antiguard;
+ delete me->members;
+ }
+ PyObject_Del( self );
+}
+
+PyObject* PyUNO_iterator_iter( PyObject *self )
+{
+ Py_INCREF( self );
+ return self;
+}
+
+PyObject* PyUNO_iterator_next( PyObject *self )
+{
+ PyUNO_iterator* me = reinterpret_cast<PyUNO_iterator*>(self);
+
+ Runtime runtime;
+ sal_Bool hasMoreElements = sal_False;
+ Any aRet;
+
+ try
+ {
+ {
+ 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 NULL;
+ }
+ catch( com::sun::star::container::NoSuchElementException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( com::sun::star::script::CannotConvertException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( com::sun::star::lang::IllegalArgumentException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::lang::WrappedTargetException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return NULL;
+}
+
+
+static PyTypeObject PyUNO_iterator_Type =
+{
+ PyVarObject_HEAD_INIT( &PyType_Type, 0 )
+ "PyUNO_iterator",
+ sizeof (PyUNO_iterator),
+ 0,
+ PyUNO_iterator_del,
+ nullptr,
+ 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
+#endif
+};
+
+PyObject* PyUNO_iterator_new( const Reference< XEnumeration > xEnumeration )
+{
+ PyUNO_iterator* self = PyObject_New( PyUNO_iterator, &PyUNO_iterator_Type );
+ if ( self == NULL )
+ return NULL; // == error
+ self->members = new PyUNO_iterator_Internals();
+ self->members->xEnumeration = xEnumeration;
+ return reinterpret_cast<PyObject*>(self);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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 );
+}
+
+
+PyObject* PyUNO_list_iterator_next( PyObject *self )
+{
+ PyUNO_list_iterator* me = reinterpret_cast<PyUNO_list_iterator*>(self);
+
+ Runtime runtime;
+ Any aRet;
+ bool noMoreElements = false;
+
+ try
+ {
+ {
+ PyThreadDetach antiguard;
+ try {
+ aRet = me->members->xIndexAccess->getByIndex( me->members->index );
+ }
+ catch( com::sun::star::lang::IndexOutOfBoundsException )
+ {
+ noMoreElements = true;
+ }
+ }
+
+ if ( noMoreElements )
+ {
+ PyErr_SetString( PyExc_StopIteration, "" );
+ return NULL;
+ }
+
+ PyRef rRet = runtime.any2PyObject( aRet );
+ me->members->index++;
+ return rRet.getAcquired();
+ }
+ catch( com::sun::star::script::CannotConvertException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( com::sun::star::lang::IllegalArgumentException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::lang::WrappedTargetException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+ catch( const ::com::sun::star::uno::RuntimeException &e )
+ {
+ raisePyExceptionWithAny( com::sun::star::uno::makeAny( e ) );
+ }
+
+ return NULL;
+}
+
+
+static PyTypeObject PyUNO_list_iterator_Type =
+{
+ PyVarObject_HEAD_INIT( &PyType_Type, 0 )
+ "PyUNO_iterator",
+ sizeof (PyUNO_list_iterator),
+ 0,
+ PyUNO_list_iterator_del,
+ nullptr,
+ 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
+#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 == NULL )
+ return NULL; // == 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
index c2187afcccd8..722e9226b1a7 100644
--- a/pyuno/source/module/pyuno_module.cxx
+++ b/pyuno/source/module/pyuno_module.cxx
@@ -180,18 +180,22 @@ static void fillStruct(
inv->setValue( pMemberName, a );
}
}
- for ( int i = 0; i < nMembers ; ++i)
+ if ( PyTuple_Size( initializer ) > 0 )
{
- const OUString memberName (pCompType->ppMemberNames[i]);
- if ( ! state.isInitialised( memberName ) )
+ // Allow partial initialisation when only keyword arguments are given
+ for ( int i = 0; i < nMembers ; ++i)
{
- OUStringBuffer buf;
- buf.appendAscii( "pyuno._createUnoStructHelper: member '");
- buf.append(memberName);
- buf.appendAscii( "' of struct type '");
- buf.append(pCompType->aBase.pTypeName);
- buf.appendAscii( "' not given a value.");
- throw RuntimeException(buf.makeStringAndClear());
+ const OUString memberName (pCompType->ppMemberNames[i]);
+ if ( ! state.isInitialised( memberName ) )
+ {
+ OUStringBuffer buf;
+ buf.appendAscii( "pyuno._createUnoStructHelper: member '");
+ buf.append(memberName);
+ buf.appendAscii( "' of struct type '");
+ buf.append(pCompType->aBase.pTypeName);
+ buf.appendAscii( "' not given a value.");
+ throw RuntimeException(buf.makeStringAndClear());
+ }
}
}
}
diff --git a/pyuno/source/module/pyuno_runtime.cxx b/pyuno/source/module/pyuno_runtime.cxx
index 8b927a6c2514..9d69797730d5 100644
--- a/pyuno/source/module/pyuno_runtime.cxx
+++ b/pyuno/source/module/pyuno_runtime.cxx
@@ -30,6 +30,7 @@
#include <rtl/ustrbuf.hxx>
#include <rtl/bootstrap.hxx>
+#include <list>
#include <typelib/typedescription.hxx>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
@@ -620,6 +621,39 @@ lcl_ExceptionMessage(PyObject *const o, OUString const*const 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::list<Any> items;
+ do
+ {
+ PyRef rItem( pItem, SAL_NO_ACQUIRE );
+ items.push_back( pyObject2Any( rItem.get() ) );
+ }
+ while( (pItem = PyIter_Next( pObj )) );
+ Sequence<Any> aSeq( items.size() );
+ ::std::list<Any>::iterator it = items.begin();
+ for( int i = 0; it != items.end(); ++it )
+ aSeq[i++] = *it;
+ a <<= aSeq;
+ return true;
+}
+
Any Runtime::pyObject2Any ( const PyRef & source, enum ConversionMode mode ) const
throw ( com::sun::star::uno::RuntimeException )
{
@@ -728,7 +762,17 @@ Any Runtime::pyObject2Any ( const PyRef & source, enum ConversionMode mode ) con
}
a <<= s;
}
- else
+ else if (PyList_Check (o))
+ {
+ Py_ssize_t l = PyList_Size (o);
+ Sequence<Any> s (l);
+ for (int i = 0; i < l; i++)
+ {
+ s[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