Hello community,

here is the log from the commit of package python3-openpyxl for 
openSUSE:Factory checked in at 2016-11-28 15:09:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python3-openpyxl (Old)
 and      /work/SRC/openSUSE:Factory/.python3-openpyxl.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python3-openpyxl"

Changes:
--------
--- /work/SRC/openSUSE:Factory/python3-openpyxl/python3-openpyxl.changes        
2016-09-28 15:04:59.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.python3-openpyxl.new/python3-openpyxl.changes   
2016-11-28 15:10:12.000000000 +0100
@@ -1,0 +2,28 @@
+Sat Nov 26 18:25:12 UTC 2016 - a...@gmx.de
+
+- update to version 2.4.1:
+  * Bug fixes
+    + #643 Make checking for duplicate sheet titles case insensitive
+    + #647 Trouble handling LibreOffice files with named styles
+    + #687 Directly assigned new named styles always refer to “Normal”
+    + #690 Cannot parse print titles with multiple sheet names
+    + #691 Cannot work with macro files created by LibreOffice
+    + Prevent duplicate differential styles
+    + #694 Allow sheet titles longer than 31 characters
+    + #697 Cannot unset hyperlinks
+    + #699 Exception raised when format objects use cell references
+    + #703 Copy height and width when copying comments
+    + #705 Incorrect content type for VBA macros
+    + #707 IndexError raised in read-only mode when accessing
+       individual cells
+    + #711 Files with external links become corrupted
+    + #715 Cannot read files containing macro sheets
+    + #717 Details from named styles not preserved when reading files
+    + #722 Remove broken Print Title and Print Area definitions
+  * Minor changes
+    + Add support for Python 3.6
+    + Correct documentation for headers and footers
+  * Deprecations
+    + Worksheet methods get_named_range() and get_sqaured_range()
+
+-------------------------------------------------------------------

Old:
----
  openpyxl-2.4.0.tar.gz

New:
----
  openpyxl-2.4.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python3-openpyxl.spec ++++++
--- /var/tmp/diff_new_pack.9XZIY6/_old  2016-11-28 15:10:13.000000000 +0100
+++ /var/tmp/diff_new_pack.9XZIY6/_new  2016-11-28 15:10:13.000000000 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python3-openpyxl
-Version:        2.4.0
+Version:        2.4.1
 Release:        0
 Summary:        A Python library to read/write Excel 2007 xlsx/xlsm files
 License:        MIT and Python-2.0

++++++ openpyxl-2.4.0.tar.gz -> openpyxl-2.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/AUTHORS.rst 
new/openpyxl-2.4.1/AUTHORS.rst
--- old/openpyxl-2.4.0/AUTHORS.rst      2016-09-15 10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/AUTHORS.rst      2016-11-23 15:13:45.000000000 +0100
@@ -23,6 +23,7 @@
 * Brice Gelineau
 * Alex Gronholm
 * Yaroslav Halchenko
+* Fumito Hamamura
 * Khchine Hamza
 * Josh Haywood
 * Jeff Holman
@@ -34,6 +35,7 @@
 * Chi Ho Kwok
 * Yingjie Lan
 * Detlef Lannert
+* Laurent Laporte
 * Nicholas Laver
 * Greg Lehmann
 * Adam Lofts
@@ -60,6 +62,7 @@
 * Dieter Vandenbussche
 * Paul Van Der Linden
 * Gerald Van Huffelen
+* Koert van der Veer
 * Laurent Vasseur
 * Kay Webber
 * Shibukawa Yoshiki
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/PKG-INFO new/openpyxl-2.4.1/PKG-INFO
--- old/openpyxl-2.4.0/PKG-INFO 2016-09-15 10:25:11.000000000 +0200
+++ new/openpyxl-2.4.1/PKG-INFO 2016-11-23 15:16:29.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: openpyxl
-Version: 2.4.0
+Version: 2.4.1
 Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
 Home-page: http://openpyxl.readthedocs.org
 Author: See AUTHORS
@@ -70,4 +70,5 @@
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Requires: python (>=2.6.0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/.constants.json 
new/openpyxl-2.4.1/openpyxl/.constants.json
--- old/openpyxl-2.4.0/openpyxl/.constants.json 2016-09-15 10:23:12.000000000 
+0200
+++ new/openpyxl-2.4.1/openpyxl/.constants.json 2016-11-23 15:13:45.000000000 
+0100
@@ -4,5 +4,5 @@
     "__license__": "MIT/Expat",
     "__maintainer_email__": "openpyxl-us...@googlegroups.com",
     "__url__": "http://openpyxl.readthedocs.org";,
-    "__version__": "2.4.0"
+    "__version__": "2.4.1"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/cell/cell.py 
new/openpyxl-2.4.1/openpyxl/cell/cell.py
--- old/openpyxl-2.4.0/openpyxl/cell/cell.py    2016-09-15 10:23:12.000000000 
+0200
+++ new/openpyxl-2.4.1/openpyxl/cell/cell.py    2016-11-23 15:13:45.000000000 
+0100
@@ -12,6 +12,7 @@
 __docformat__ = "restructuredtext en"
 
 # Python stdlib imports
+from copy import copy
 import datetime
 import re
 
@@ -306,13 +307,17 @@
         """Set value and display for hyperlinks in a cell.
         Automatically sets the `value` of the cell with link text,
         but you can modify it afterwards by setting the `value`
-        property, and the hyperlink will remain."""
-        if not isinstance(val, Hyperlink):
-            val = Hyperlink(ref="", target=val)
-        val.ref = self.coordinate
-        self._hyperlink = val
-        if self._value is None:
-            self.value = val.target or val.location
+        property, and the hyperlink will remain.
+        Hyperlink is removed if set to ``None``."""
+        if val is None:
+            self._hyperlink = None
+        else:
+            if not isinstance(val, Hyperlink):
+                val = Hyperlink(ref="", target=val)
+            val.ref = self.coordinate
+            self._hyperlink = val
+            if self._value is None:
+                self.value = val.target or val.location
 
 
     @property
@@ -384,13 +389,19 @@
         """
         return self._comment
 
+
     @comment.setter
     def comment(self, value):
+        """
+        Assign a comment to a cell
+        """
 
         if value is not None:
-            value.parent = self
+            if value.parent:
+                value = copy(value)
+            value.bind(self)
         elif value is None and self._comment:
-            self._comment.parent = None
+            self._comment.unbind()
         self._comment = value
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/comments/comments.py 
new/openpyxl-2.4.1/openpyxl/comments/comments.py
--- old/openpyxl-2.4.0/openpyxl/comments/comments.py    2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/comments/comments.py    2016-11-23 
15:13:45.000000000 +0100
@@ -1,8 +1,6 @@
 from __future__ import absolute_import
 # Copyright (c) 2010-2016 openpyxl
 
-from openpyxl.compat import deprecated
-
 
 class Comment(object):
 
@@ -29,13 +27,31 @@
         return "Comment: {0} by {1}".format(self.content, self.author)
 
 
-    @parent.setter
-    def parent(self, cell):
+    def __copy__(self):
+        """Create a detached copy of this comment."""
+        clone = self.__class__(self.content, self.author)
+        clone.width = self.width
+        clone.height = self.height
+        return clone
+
+
+    def bind(self, cell):
+        """
+        Bind comment to a particular cell
+        """
         if cell is not None and self._parent is not None and self._parent != 
cell:
-            raise AttributeError("Comment already assigned to %s in worksheet 
%s. Cannot assign a comment to more than one cell" % (cell.coordinate, 
cell.parent.title))
+            fmt = "Comment already assigned to {0} in worksheet {1}. Cannot 
assign a comment to more than one cell"
+            raise AttributeError(fmt.format(cell.coordinate, 
cell.parent.title))
         self._parent = cell
 
 
+    def unbind(self):
+        """
+        Unbind a comment from a cell
+        """
+        self._parent = None
+
+
     @property
     def text(self):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/drawing/text.py 
new/openpyxl-2.4.1/openpyxl/drawing/text.py
--- old/openpyxl-2.4.0/openpyxl/drawing/text.py 2016-03-16 10:34:54.000000000 
+0100
+++ new/openpyxl-2.4.1/openpyxl/drawing/text.py 2016-11-23 15:13:45.000000000 
+0100
@@ -118,7 +118,7 @@
     kumimoji = Bool(allow_none=True)
     lang = String(allow_none=True)
     altLang = String(allow_none=True)
-    sz = Integer(allow_none=True)
+    sz = MinMax(allow_none=True, min=100, max=400000) # 100ths of a point
     b = Bool(allow_none=True)
     i = Bool(allow_none=True)
     u = NoneSet(values=(['words', 'sng', 'dbl', 'heavy', 'dotted',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/formatting/rule.py 
new/openpyxl-2.4.1/openpyxl/formatting/rule.py
--- old/openpyxl-2.4.0/openpyxl/formatting/rule.py      2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/formatting/rule.py      2016-11-23 
15:13:45.000000000 +0100
@@ -18,14 +18,21 @@
 from openpyxl.styles.colors import Color, ColorDescriptor
 from openpyxl.styles.differential import DifferentialStyle
 
+from openpyxl.utils.cell import COORD_RE
+
 
 class ValueDescriptor(Float):
     """
     Expected type depends upon type attribue of parent :-(
+
+    Most values should be numeric BUT they can also be cell references
     """
 
     def __set__(self, instance, value):
-        if instance.type == "formula":
+        ref = None
+        if value is not None and isinstance(value, basestring):
+            ref = COORD_RE.match(value)
+        if instance.type == "formula" or ref:
             self.expected_type = basestring
         else:
             self.expected_type = float
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/packaging/manifest.py 
new/openpyxl-2.4.1/openpyxl/packaging/manifest.py
--- old/openpyxl-2.4.0/openpyxl/packaging/manifest.py   2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/packaging/manifest.py   2016-11-23 
15:13:45.000000000 +0100
@@ -40,7 +40,7 @@
 mimetypes.init()
 mimetypes.add_type('application/xml', ".xml")
 mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', 
".rels")
-mimetypes.add_type("application/vnd.ms-office.activeX", ".bin")
+mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin")
 mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", 
".vml")
 mimetypes.add_type("image/x-emf", ".emf")
 
@@ -112,8 +112,12 @@
 
     @property
     def extensions(self):
+        """
+        Map content types to file extensions
+        Skip parts without extensions
+        """
         exts = set([os.path.splitext(part.PartName)[-1] for part in 
self.Override])
-        return [(ext[1:], mimetypes.types_map[ext]) for ext in sorted(exts)]
+        return [(ext[1:], mimetypes.types_map[ext]) for ext in sorted(exts) if 
ext]
 
 
     def to_tree(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/packaging/relationship.py 
new/openpyxl-2.4.1/openpyxl/packaging/relationship.py
--- old/openpyxl-2.4.0/openpyxl/packaging/relationship.py       2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/packaging/relationship.py       2016-11-23 
15:13:45.000000000 +0100
@@ -30,7 +30,7 @@
     target = Alias("Target")
     TargetMode = String(allow_none=True)
     Id = String(allow_none=True)
-    id = Alias("id")
+    id = Alias("Id")
 
 
     def __init__(self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/packaging/workbook.py 
new/openpyxl-2.4.1/openpyxl/packaging/workbook.py
--- old/openpyxl-2.4.0/openpyxl/packaging/workbook.py   2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/packaging/workbook.py   2016-11-23 
15:13:45.000000000 +0100
@@ -6,6 +6,7 @@
 """
 
 import posixpath
+from warnings import warn
 
 from openpyxl.xml.constants import (
     ARC_WORKBOOK,
@@ -57,12 +58,23 @@
             )
 
         if package.definedNames:
+            package.definedNames._cleanup()
             self.wb.defined_names = package.definedNames
 
 
     def find_sheets(self):
+        """
+        Find all sheets in the workbook and return the link to the source file.
+
+        Older XLSM files sometimes contain invalid sheet elements.
+        Warn user when these are removed.
+        """
 
         for sheet in self.sheets:
+            if not sheet.id:
+                msg = "File contains an invalid specification for {0}. This 
will be removed".format(sheet.name)
+                warn(msg)
+                continue
             yield sheet, self.rels[sheet.id]
 
 
@@ -71,6 +83,7 @@
         Bind reserved names to parsed worksheets
         """
         defns = []
+
         for defn in self.wb.defined_names.definedName:
             reserved = defn.is_reserved
             if reserved in ("Print_Titles", "Print_Area"):
@@ -81,7 +94,6 @@
                     sheet.print_title_cols = cols
                 elif reserved == "Print_Area":
                     sheet.print_area = _unpack_print_area(defn)
-                continue
             else:
                 defns.append(defn)
         self.wb.defined_names.definedName = defns
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/styles/differential.py 
new/openpyxl-2.4.1/openpyxl/styles/differential.py
--- old/openpyxl-2.4.0/openpyxl/styles/differential.py  2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/styles/differential.py  2016-11-23 
15:13:45.000000000 +0100
@@ -53,6 +53,9 @@
 
 
 class DifferentialStyleList(Serialisable):
+    """
+    Deduping container for differential styles.
+    """
 
     tagname = "dxfs"
 
@@ -65,14 +68,22 @@
 
 
     def append(self, dxf):
-        styles = self.styles
-        styles.append(dxf)
-        self.styles = styles
+        """
+        Check to see whether style already exists and append it if does not.
+        """
+        if not isinstance(dxf, DifferentialStyle):
+            raise TypeError('expected ' + str(DifferentialStyle))
+        if dxf in self.styles:
+            return
+        self.styles.append(dxf)
 
 
     def add(self, dxf):
+        """
+        Add a differential style and return its index
+        """
         self.append(dxf)
-        return len(self.styles) - 1
+        return self.styles.index(dxf)
 
 
     def __bool__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/styles/named_styles.py 
new/openpyxl-2.4.1/openpyxl/styles/named_styles.py
--- old/openpyxl-2.4.0/openpyxl/styles/named_styles.py  2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/styles/named_styles.py  2016-11-23 
15:13:45.000000000 +0100
@@ -69,10 +69,10 @@
         self.protection = protection
         self.builtinId = builtinId
         self.hidden = hidden
-        self.xfId = xfId # index
         self._wb = None
         self._style = StyleArray()
 
+
     def __setattr__(self, attr, value):
         super(NamedStyle, self).__setattr__(attr, value)
         if getattr(self, '_wb', None) and attr in (
@@ -88,6 +88,21 @@
                 yield key, safe_string(value)
 
 
+    @property
+    def xfId(self):
+        """
+        Index of the style in the list of named styles
+        """
+        return self._style.xfId
+
+
+    def _set_index(self, idx):
+        """
+        Allow the containing list to set the index
+        """
+        self._style.xfId = idx
+
+
     def bind(self, wb):
         """
         Bind a named style to a workbook
@@ -143,6 +158,9 @@
 class NamedStyleList(list):
     """
     Named styles are editable and can be applied to multiple objects
+
+    As only the index is stored in referencing objects the order mus
+    be preserved.
     """
 
     @property
@@ -168,17 +186,10 @@
             raise TypeError("""Only NamedStyle instances can be added""")
         elif style.name in self.names:
             raise ValueError("""Style {0} exists already""".format(style.name))
+        style._set_index(len(self))
         super(NamedStyleList, self).append(style)
 
 
-    def add(self, style):
-        """
-        Add a style and return index
-        """
-        self.append(style)
-        return self.index(style)
-
-
 class _NamedCellStyle(Serialisable):
 
     """
@@ -247,19 +258,28 @@
     @property
     def names(self):
         """
-        Convert to NamedStyle objects and remove duplicates
+        Convert to NamedStyle objects and remove duplicates.
+
+        In theory the highest xfId wins but in practice they are duplicates
+        so it doesn't matter.
         """
 
         def sort_fn(v):
             return v.xfId
 
-        styles = OrderedDict()
+        styles = NamedStyleList()
+        names = set()
+
         for ns in sorted(self.cellStyle, key=sort_fn):
+            if ns.name in names:
+                continue
+
             style = NamedStyle(
                 name=ns.name,
-                hidden=ns.hidden
+                hidden=ns.hidden,
+                builtinId = ns.builtinId
             )
-            style.builtinId = ns.builtinId
-            style.xfId = ns.xfId
-            styles[ns.name] = style
-        return NamedStyleList(styles.values())
+            names.add(ns.name)
+            styles.append(style) # assign xfId
+
+        return styles
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/styles/styleable.py 
new/openpyxl-2.4.1/openpyxl/styles/styleable.py
--- old/openpyxl-2.4.0/openpyxl/styles/styleable.py     2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/styles/styleable.py     2016-11-23 
15:13:45.000000000 +0100
@@ -72,7 +72,7 @@
             if style not in coll:
                 instance.parent.parent.add_named_style(style)
         elif value not in coll.names:
-            if value in styles:
+            if value in styles: # is it builtin?
                 style = styles[value]
                 if style not in coll:
                     instance.parent.parent.add_named_style(style)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/styles/stylesheet.py 
new/openpyxl-2.4.1/openpyxl/styles/stylesheet.py
--- old/openpyxl-2.4.0/openpyxl/styles/stylesheet.py    2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/styles/stylesheet.py    2016-11-23 
15:13:45.000000000 +0100
@@ -87,7 +87,7 @@
         self.alignments = self.cellXfs.alignments
         self.protections = self.cellXfs.prots
         self._normalise_numbers()
-        self.named_styles =  self._merge_named_styles()
+        self.named_styles = self._merge_named_styles()
 
 
     @classmethod
@@ -96,30 +96,39 @@
         attrs = dict(node.attrib)
         for k in attrs:
             del node.attrib[k]
-        return  super(Stylesheet, cls).from_tree(node)
+        return super(Stylesheet, cls).from_tree(node)
 
 
     def _merge_named_styles(self):
         """
-        Merge named style names "cellStyles" with their associated styles 
"cellStyleXfs"
+        Merge named style names "cellStyles" with their associated styles
+        "cellStyleXfs"
         """
         named_styles = self.cellStyles.names
-        custom = self.custom_formats
-        formats = self.number_formats
+
         for style in named_styles:
-            xf = self.cellStyleXfs[style.xfId]
-            style.font = self.fonts[xf.fontId]
-            style.fill = self.fills[xf.fillId]
-            style.border = self.borders[xf.borderId]
-            if xf.numFmtId in custom:
-                style.number_format = custom[xf.numFmtId]
-            if xf.alignment:
-                style.alignment = xf.alignment
-            if xf.protection:
-                style.protection = xf.protection
+            self._expand_named_style(style)
+
         return named_styles
 
 
+    def _expand_named_style(self, named_style):
+        """
+        Bind format definitions for a named style from the associated style
+        record
+        """
+        xf = self.cellStyleXfs[named_style.xfId]
+        named_style.font = self.fonts[xf.fontId]
+        named_style.fill = self.fills[xf.fillId]
+        named_style.border = self.borders[xf.borderId]
+        if xf.numFmtId in self.custom_formats:
+            named_style.number_format = self.custom_formats[xf.numFmtId]
+        if xf.alignment:
+            named_style.alignment = xf.alignment
+        if xf.protection:
+            named_style.protection = xf.protection
+
+
     def _split_named_styles(self, wb):
         """
         Convert NamedStyle into separate CellStyle and Xf objects
@@ -165,6 +174,9 @@
 
     wb._cell_styles = stylesheet.cell_styles
     wb._named_styles = stylesheet.named_styles
+    for ns in wb._named_styles:
+        ns.bind(wb)
+
     wb._borders = IndexedList(stylesheet.borders)
     wb._fonts = IndexedList(stylesheet.fonts)
     wb._fills = IndexedList(stylesheet.fills)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/utils/cell.py 
new/openpyxl-2.4.1/openpyxl/utils/cell.py
--- old/openpyxl-2.4.0/openpyxl/utils/cell.py   2016-09-15 10:23:12.000000000 
+0200
+++ new/openpyxl-2.4.1/openpyxl/utils/cell.py   2016-11-23 15:13:45.000000000 
+0100
@@ -21,7 +21,7 @@
 """
 ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE)
 SHEET_TITLE = """
-^(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^']*))!"""
+(('(?P<quoted>([^']|'')*)')|(?P<notquoted>[^']*))!"""
 SHEETRANGE_RE = re.compile("""{0}(?P<cells>{1})$""".format(
     SHEET_TITLE, RANGE_EXPR), re.VERBOSE)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/workbook/child.py 
new/openpyxl-2.4.1/openpyxl/workbook/child.py
--- old/openpyxl-2.4.0/openpyxl/workbook/child.py       2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/workbook/child.py       2016-11-23 
15:13:45.000000000 +0100
@@ -2,6 +2,8 @@
 # Copyright (c) 2010-2016 openpyxl
 
 import re
+import warnings
+
 from openpyxl.compat import unicode
 
 from openpyxl.worksheet.header_footer import HeaderFooter
@@ -17,10 +19,13 @@
     """
     Naive check to see whether name already exists.
     If name does exist suggest a name using an incrementer
+    Duplicates are case insensitive
     """
-    if value in names:
+    # Check for an absolute match in which case we need to find an alternative
+    match = [n for n in names if n.lower() == value.lower()]
+    if match:
         names = ",".join(names)
-        sheet_title_regex = re.compile("(?P<title>%s)(?P<count>\d*),?" % 
re.escape(value))
+        sheet_title_regex = re.compile("(?P<title>%s)(?P<count>\d*),?" % 
re.escape(value), re.I)
         matches = sheet_title_regex.findall(names)
         if matches:
             # use name, but append with the next highest integer
@@ -90,7 +95,7 @@
             value = avoid_duplicate_name(self.parent.sheetnames, value)
 
         if len(value) > 31:
-            raise ValueError('Maximum 31 characters allowed in sheet title')
+            warnings.warn("Title is more than 31 characters. Some applications 
may not be able to read the file")
 
         self.__title = value
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/workbook/defined_name.py 
new/openpyxl-2.4.1/openpyxl/workbook/defined_name.py
--- old/openpyxl-2.4.0/openpyxl/workbook/defined_name.py        2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/workbook/defined_name.py        2016-11-23 
15:13:45.000000000 +0100
@@ -33,7 +33,7 @@
 COL_RANGE_RE = re.compile(COL_RANGE)
 ROW_RANGE = r"""(?P<rows>[$]?\d+:[$]?\d+)"""
 ROW_RANGE_RE = re.compile(ROW_RANGE)
-TITLES_REGEX = re.compile("""^{0}{1}?,?{2}?$""".format(SHEET_TITLE, ROW_RANGE, 
COL_RANGE),
+TITLES_REGEX = re.compile("""{0}{1}?,?{2}?""".format(SHEET_TITLE, ROW_RANGE, 
COL_RANGE),
                           re.VERBOSE)
 
 
@@ -44,8 +44,11 @@
     Extract rows and or columns from print titles so that they can be
     assigned to a worksheet
     """
-    m = TITLES_REGEX.match(defn.value)
-    return m.group('rows'), m.group('cols')
+    scanner = TITLES_REGEX.finditer(defn.value)
+    kw = dict((k, v) for match in scanner
+              for k, v in match.groupdict().items() if v)
+
+    return kw.get('rows'), kw.get('cols')
 
 
 def _unpack_print_area(defn):
@@ -137,7 +140,8 @@
             for part in tok.items:
                 if part.subtype == "RANGE":
                     m = SHEETRANGE_RE.match(part.value)
-                    yield m.group('notquoted'), m.group('cells')
+                    sheetname = m.group('notquoted') or m.group('quoted')
+                    yield sheetname, m.group('cells')
 
 
     @property
@@ -174,6 +178,14 @@
         self.definedName = definedName
 
 
+    def _cleanup(self):
+        """
+        Strip broken or unknown definitions
+        """
+        self.delete("_xlnm.Print_Titles")
+        self.delete("_xlnm.Print_Area")
+
+
     def _duplicate(self, defn):
         """
         Check for whether DefinedName with the same name and scope already
@@ -199,20 +211,46 @@
 
 
     def __contains__(self, name):
+        """
+        See if a globaly defined name exists
+        """
         for defn in self.definedName:
-            if defn.name == name:
+            if defn.name == name and defn.localSheetId is None:
                 return True
 
 
     def __getitem__(self, name):
+        """
+        Get globally defined name
+        """
+        defn = self.get(name)
+        if not defn:
+            raise KeyError("No definition called {0}".format(name))
+        return defn
+
+
+    def get(self, name, scope=None):
+        """
+        Get the name assigned to a specicic sheet or global
+        """
         for defn in self.definedName:
-            if defn.name == name:
+            if defn.name == name and defn.localSheetId == scope:
                 return defn
-        raise KeyError("No definition called {0}".format(name))
 
 
     def __delitem__(self, name):
+        """
+        Delete a globally defined name
+        """
+        if not self.delete(name):
+            raise KeyError("No globally defined name {0}".format(name))
+
+
+    def delete(self, name, scope=None):
+        """
+        Delete a name assigned to a specific or global
+        """
         for idx, defn in enumerate(self.definedName):
-            if defn.name == name:
+            if defn.name == name and defn.localSheetId == scope:
                 del self.definedName[idx]
-                break
+                return True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/workbook/workbook.py 
new/openpyxl-2.4.1/openpyxl/workbook/workbook.py
--- old/openpyxl-2.4.0/openpyxl/workbook/workbook.py    2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/workbook/workbook.py    2016-11-23 
15:13:45.000000000 +0100
@@ -272,7 +272,7 @@
         """
         Add a named style
         """
-        style.xfId = self._named_styles.add(style)
+        self._named_styles.append(style)
         style.bind(self)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/worksheet/copier.py 
new/openpyxl-2.4.1/openpyxl/worksheet/copier.py
--- old/openpyxl-2.4.0/openpyxl/worksheet/copier.py     2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/worksheet/copier.py     2016-11-23 
15:13:45.000000000 +0100
@@ -57,7 +57,7 @@
                 target_cell._hyperlink = copy(source_cell.hyperlink)
 
             if source_cell.comment:
-                target_cell.comment = Comment(source_cell.comment.text, 
source_cell.comment.author)
+                target_cell.comment = copy(source_cell.comment)
 
 
     def _copy_dimensions(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/worksheet/header_footer.py 
new/openpyxl-2.4.1/openpyxl/worksheet/header_footer.py
--- old/openpyxl-2.4.0/openpyxl/worksheet/header_footer.py      2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/worksheet/header_footer.py      2016-11-23 
15:13:45.000000000 +0100
@@ -7,6 +7,7 @@
 from warnings import warn
 
 from openpyxl.descriptors import (
+    Alias,
     Bool,
     Strict,
     String,
@@ -138,6 +139,7 @@
 
     left = Typed(expected_type=_HeaderFooterPart)
     center = Typed(expected_type=_HeaderFooterPart)
+    centre = Alias("center")
     right = Typed(expected_type=_HeaderFooterPart)
 
     __keys = ('L', 'C', 'R')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/worksheet/read_only.py 
new/openpyxl-2.4.1/openpyxl/worksheet/read_only.py
--- old/openpyxl-2.4.0/openpyxl/worksheet/read_only.py  2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/worksheet/read_only.py  2016-11-23 
15:13:45.000000000 +0100
@@ -74,7 +74,6 @@
         # Methods from Worksheet
         self.cell = Worksheet.cell.__get__(self)
         self.iter_rows = Worksheet.iter_rows.__get__(self)
-        self.rows = Worksheet.rows.__get__(self)
 
 
     def __getitem__(self, key):
@@ -179,12 +178,17 @@
 
     def _get_cell(self, row, column):
         """Cells are returned by a generator which can be empty"""
-        cell = tuple(self.get_squared_range(column, row, column, row))[0]
-        if cell:
-            return cell[0]
+        for row in self.get_squared_range(column, row, column, row):
+            if row:
+                return row[0]
         return EMPTY_CELL
 
 
+    @property
+    def rows(self):
+        return self.iter_rows()
+
+
     def calculate_dimension(self, force=False):
         if not all([self.max_column, self.max_row]):
             if force:
@@ -202,8 +206,11 @@
         Loop through all the cells to get the size of a worksheet.
         Do this only if it is explicitly requested.
         """
+
         max_col = 0
         for r in self.rows:
+            if not r:
+                continue
             cell = r[-1]
             max_col = max(max_col, cell.column)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/worksheet/worksheet.py 
new/openpyxl-2.4.1/openpyxl/worksheet/worksheet.py
--- old/openpyxl-2.4.0/openpyxl/worksheet/worksheet.py  2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/worksheet/worksheet.py  2016-11-23 
15:13:45.000000000 +0100
@@ -285,9 +285,12 @@
         :param coordinate: coordinates of the cell (e.g. 'B12')
         :type coordinate: string
 
+        :param value: value of the cell (e.g. 5)
+        :type value: numeric or time or string or bool or none
+
         :raise: InsufficientCoordinatesException when neither row nor column 
are not given
 
-        :rtype: :class:openpyxl.cell.Cell
+        :rtype: openpyxl.cell.Cell
 
         """
 
@@ -464,6 +467,9 @@
 
         Additional rows and columns can be created using offsets.
 
+        :param range_string: range string (e.g. 'A1:B2') *deprecated*
+        :type range_string: string
+
         :param min_col: smallest column index (1-based index)
         :type min_col: int
 
@@ -477,10 +483,10 @@
         :type max_row: int
 
         :param row_offset: additional rows (e.g. 4)
-        :type row: int
+        :type row_offset: int
 
-        :param column_offset: additonal columns (e.g. 3)
-        :type column: int
+        :param column_offset: additional columns (e.g. 3)
+        :type column_offset: int
 
         :rtype: generator
         """
@@ -573,6 +579,10 @@
         return self.iter_cols()
 
 
+    @deprecated("""
+    Use ws.iter_rows() or ws.iter_cols() depending whether you
+    want rows or columns returned.
+    """)
     def get_squared_range(self, min_col, min_row, max_col, max_row):
         """Returns a 2D array of cells. Will create any cells within the
         boundaries that do not already exist
@@ -597,6 +607,7 @@
                         for column in range(min_col, max_col + 1))
 
 
+    @deprecated("""Ranges are workbook objects. Use 
wb.defined_names[range_name]""")
     def get_named_range(self, range_name):
         """
         Returns a 2D array of cells, with optional row and column offsets.
@@ -604,7 +615,7 @@
         :param range_name: `named range` name
         :type range_name: string
 
-        :rtype: tuples of tuples of :class:`openpyxl.cell.Cell`
+        :rtype: tuple[tuple[openpyxl.cell.Cell]]
         """
         defn = self.parent.defined_names[range_name]
         if defn.localSheetId and defn.localSheetId != 
self.parent.get_index(self):
@@ -747,7 +758,7 @@
         * If it's a dict: values are assigned to the columns indicated by the 
keys (numbers or letters)
 
         :param iterable: list, range or generator, or dict containing values 
to append
-        :type iterable: list/tuple/range/generator or dict
+        :type iterable: list|tuple|range|generator or dict
 
         Usage:
 
@@ -799,6 +810,10 @@
         """ tells which cell is under the given coordinates (in pixels)
         counting from the top-left corner of the sheet.
         Can be used to locate images and charts on the worksheet """
+
+        if left < 0 or top < 0:
+            raise ValueError("Coordinates must be positive")
+
         current_col = 1
         current_row = 1
         column_dimensions = self.column_dimensions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/writer/workbook.py 
new/openpyxl-2.4.1/openpyxl/writer/workbook.py
--- old/openpyxl-2.4.0/openpyxl/writer/workbook.py      2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/writer/workbook.py      2016-11-23 
15:13:45.000000000 +0100
@@ -104,11 +104,11 @@
     # external references
     for link in wb._external_links:
         # need to match a counter with a workbook's relations
-        rId = len(wb.rels)
-        ext = ExternalReference(id="rId{0}".format(link._id))
+        rId = len(wb.rels) + 1
         rel = Relationship(type=link._rel_type, Target=link.path)
-        root.externalReferences.append(ext)
         wb.rels.append(rel)
+        ext = ExternalReference(id=rel.id)
+        root.externalReferences.append(ext)
 
     # Defined names
     defined_names = copy(wb.defined_names) # don't add special defns to 
workbook itself.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl/writer/worksheet.py 
new/openpyxl-2.4.1/openpyxl/writer/worksheet.py
--- old/openpyxl-2.4.0/openpyxl/writer/worksheet.py     2016-09-15 
10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl/writer/worksheet.py     2016-11-23 
15:13:45.000000000 +0100
@@ -42,10 +42,11 @@
 
 def write_conditional_formatting(worksheet):
     """Write conditional formatting to xml."""
+    df = DifferentialStyle()
     wb = worksheet.parent
     for cf in worksheet.conditional_formatting:
         for rule in cf.rules:
-            if rule.dxf and rule.dxf != DifferentialStyle():
+            if rule.dxf and rule.dxf != df:
                 rule.dxfId = wb._differential_styles.add(rule.dxf)
         yield cf.to_tree()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/openpyxl.egg-info/PKG-INFO 
new/openpyxl-2.4.1/openpyxl.egg-info/PKG-INFO
--- old/openpyxl-2.4.0/openpyxl.egg-info/PKG-INFO       2016-09-15 
10:25:11.000000000 +0200
+++ new/openpyxl-2.4.1/openpyxl.egg-info/PKG-INFO       2016-11-23 
15:16:25.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: openpyxl
-Version: 2.4.0
+Version: 2.4.1
 Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
 Home-page: http://openpyxl.readthedocs.org
 Author: See AUTHORS
@@ -70,4 +70,5 @@
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Requires: python (>=2.6.0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.4.0/setup.py new/openpyxl-2.4.1/setup.py
--- old/openpyxl-2.4.0/setup.py 2016-09-15 10:23:12.000000000 +0200
+++ new/openpyxl-2.4.1/setup.py 2016-11-23 15:13:45.000000000 +0100
@@ -75,5 +75,6 @@
                  'Programming Language :: Python :: 3.3',
                  'Programming Language :: Python :: 3.4',
                  'Programming Language :: Python :: 3.5',
+                 'Programming Language :: Python :: 3.6',
                  ],
     )


Reply via email to