diff options
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 |