Hello community,

here is the log from the commit of package python3-openpyxl for 
openSUSE:Factory checked in at 2015-11-23 07:30:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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        
2015-11-05 11:35:44.000000000 +0100
+++ /work/SRC/openSUSE:Factory/.python3-openpyxl.new/python3-openpyxl.changes   
2015-11-23 07:30:14.000000000 +0100
@@ -1,0 +2,15 @@
+Sun Nov 22 00:55:36 UTC 2015 - a...@gmx.de
+
+- update to version 2.3.1:
+  * #534 Exception when using columns property in read-only mode.
+  * #536 Incorrectly handle comments from Google Docs files.
+  * #539 Flexible value types for conditional formatting.
+  * #542 Missing content types for images.
+  * #543 Make sure images fit containers on all OSes.
+  * #544 Gracefully handle missing cell styles.
+  * #546 ExternalLink duplicated when editing a file with macros.
+  * #548 Exception with non-ASCII worksheet titles
+  * #551 Combine multiple LineCharts
+  * PR 88 Fix page margins in parser.
+
+-------------------------------------------------------------------

Old:
----
  openpyxl-2.3.0.tar.gz

New:
----
  openpyxl-2.3.1.tar.gz

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

Other differences:
------------------
++++++ python3-openpyxl.spec ++++++
--- /var/tmp/diff_new_pack.FQX20y/_old  2015-11-23 07:30:15.000000000 +0100
+++ /var/tmp/diff_new_pack.FQX20y/_new  2015-11-23 07:30:15.000000000 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python3-openpyxl
-Version:        2.3.0
+Version:        2.3.1
 Release:        0
 Summary:        A Python library to read/write Excel 2007 xlsx/xlsm files
 License:        MIT and Python-2.0

++++++ openpyxl-2.3.0.tar.gz -> openpyxl-2.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/AUTHORS.rst 
new/openpyxl-2.3.1/AUTHORS.rst
--- old/openpyxl-2.3.0/AUTHORS.rst      2015-10-20 18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/AUTHORS.rst      2015-11-20 09:48:05.000000000 +0100
@@ -15,6 +15,7 @@
 * ccoacley
 * Maarten De Paepe
 * Etienne Desautels
+* Dmitriy Chernyshov
 * Eric Chlebek
 * Alexandre Fayolle
 * Eric Gazoni
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/PKG-INFO new/openpyxl-2.3.1/PKG-INFO
--- old/openpyxl-2.3.0/PKG-INFO 2015-10-20 18:26:07.000000000 +0200
+++ new/openpyxl-2.3.1/PKG-INFO 2015-11-20 09:49:39.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: openpyxl
-Version: 2.3.0
+Version: 2.3.1
 Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
 Home-page: http://openpyxl.readthedocs.org
 Author: See AUTHORS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/.constants.json 
new/openpyxl-2.3.1/openpyxl/.constants.json
--- old/openpyxl-2.3.0/openpyxl/.constants.json 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/.constants.json 2015-11-20 09:48:05.000000000 
+0100
@@ -4,5 +4,5 @@
     "__license__": "MIT/Expat",
     "__maintainer_email__": "openpyxl-us...@googlegroups.com",
     "__url__": "http://openpyxl.readthedocs.org";,
-    "__version__": "2.3.0"
+    "__version__": "2.3.1"
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/cell/text.py 
new/openpyxl-2.3.1/openpyxl/cell/text.py
--- old/openpyxl-2.3.0/openpyxl/cell/text.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/openpyxl-2.3.1/openpyxl/cell/text.py    2015-11-20 09:48:05.000000000 
+0100
@@ -0,0 +1,180 @@
+from __future__ import absolute_import
+# Copyright (c) 2010-2015 openpyxl
+
+"""
+Richtext definition
+"""
+from openpyxl.compat import unicode
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Alias,
+    Typed,
+    Integer,
+    Set,
+    Bool,
+    String,
+    Sequence,
+)
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedInteger,
+    NestedString,
+    NestedText,
+)
+from openpyxl.styles.fonts import Font
+
+
+class PhoneticProperties(Serialisable):
+
+    fontId = Integer()
+    type = Set(values=(['halfwidthKatakana', 'fullwidthKatakana', 'Hiragana',
+                        'noConversion']))
+    alignment = Set(values=(['noControl', 'left', 'center', 'distributed']))
+
+    def __init__(self,
+                 fontId=None,
+                 type=None,
+                 alignment=None,
+                ):
+        self.fontId = fontId
+        self.type = type
+        self.alignment = alignment
+
+
+class PhoneticText(Serialisable):
+
+    sb = Integer()
+    eb = Integer()
+    t = Typed(expected_type=String())
+    text = Alias('t')
+
+    def __init__(self,
+                 sb=None,
+                 eb=None,
+                 t=None,
+                ):
+        self.sb = sb
+        self.eb = eb
+        self.t = t
+
+
+class InlineFont(Font):
+
+    """
+    Font for inline text because, yes what you need are different objects with 
the same elements but different constraints.
+    """
+
+    tagname = "RPrElt"
+
+    rFont = NestedString(allow_none=True)
+    charset = Font.charset
+    family = Font.family
+    b =Font.b
+    i = Font.i
+    strike = Font.strike
+    outline = Font.outline
+    shadow = Font.shadow
+    condense = Font.condense
+    extend = Font.extend
+    color = Font.color
+    sz = Font.sz
+    u = Font.u
+    vertAlign = Font.vertAlign
+    scheme = Font.scheme
+
+    __elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike',
+                    'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 
'u',
+                    'vertAlign', 'scheme')
+
+    def __init__(self,
+                 rFont=None,
+                 charset=None,
+                 family=None,
+                 b=None,
+                 i=None,
+                 strike=None,
+                 outline=None,
+                 shadow=None,
+                 condense=None,
+                 extend=None,
+                 color=None,
+                 sz=None,
+                 u=None,
+                 vertAlign=None,
+                 scheme=None,
+                ):
+        self.rFont = rFont
+        self.charset = charset
+        self.family = family
+        self.b = b
+        self.i = i
+        self.strike = strike
+        self.outline = outline
+        self.shadow = shadow
+        self.condense = condense
+        self.extend = extend
+        self.color = color
+        self.sz = sz
+        self.u = u
+        self.vertAlign = vertAlign
+        self.scheme = scheme
+
+
+class RichText(Serialisable):
+
+    tagname = "RElt"
+
+    rPr = Typed(expected_type=InlineFont, allow_none=True)
+    font = Alias("rPr")
+    t = NestedText(expected_type=unicode, allow_none=True)
+    text = Alias("t")
+
+    __elements__ = ('rPr', 't')
+
+    def __init__(self,
+                 rPr=None,
+                 t=None,
+                ):
+        self.rPr = rPr
+        self.t = t
+
+
+class Text(Serialisable):
+
+    tagname = "text"
+
+    t = NestedText(allow_none=True, expected_type=unicode)
+    plain = Alias("t")
+    r = Sequence(expected_type=RichText, allow_none=True)
+    formatted = Alias("r")
+    rPh = Sequence(expected_type=PhoneticText, allow_none=True)
+    phonetic = Alias("rPh")
+    phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True)
+    PhoneticProperties = Alias("phoneticPr")
+
+    __elements__ = ('t', 'r', 'rPh', 'phoneticPr')
+
+    def __init__(self,
+                 t=None,
+                 r=(),
+                 rPh=(),
+                 phoneticPr=None,
+                ):
+        self.t = t
+        self.r = r
+        self.rPh = rPh
+        self.phoneticPr = phoneticPr
+
+
+    @property
+    def content(self):
+        """
+        Text stripped of all formatting
+        """
+        snippets = []
+        if self.plain is not None:
+            snippets.append(self.plain)
+        for block in self.formatted:
+            snippets.append(block.t)
+        return "".join(snippets)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/chart/_chart.py 
new/openpyxl-2.3.1/openpyxl/chart/_chart.py
--- old/openpyxl-2.3.0/openpyxl/chart/_chart.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/chart/_chart.py 2015-11-20 09:48:05.000000000 
+0100
@@ -102,12 +102,16 @@
             self.plot_area._charts.append(chart)
             idx_base += len(chart.series)
 
-        for axis in ("x_axis", "y_axis", 'z_axis'):
-            axis = getattr(self, axis, None)
-            if axis is None:
-                continue
-            ax = getattr(self.plot_area, axis.tagname)
-            ax.append(axis)
+        axIds = []
+        for axId in ("x_axis", "y_axis", 'z_axis'):
+            for chart in self._charts:
+                axis = getattr(chart, axId, None)
+                if axis is None:
+                    continue
+                if axis.axId not in axIds:
+                    ax = getattr(self.plot_area, axis.tagname)
+                    ax.append(axis)
+                    axIds.append(axis.axId)
 
         container = ChartContainer(plotArea=self.plot_area, 
legend=self.legend, title=self.title)
         if isinstance(chart, _3DBase):
@@ -157,7 +161,7 @@
             values = data.cols
 
         for v in values:
-            range_string = "{0}!{1}:{2}".format(data.sheetname, v[0], v[-1])
+            range_string = u"{0}!{1}:{2}".format(data.sheetname, v[0], v[-1])
             series = SeriesFactory(range_string, 
title_from_data=titles_from_data)
             self.ser.append(series)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/chart/reference.py 
new/openpyxl-2.3.1/openpyxl/chart/reference.py
--- old/openpyxl-2.3.0/openpyxl/chart/reference.py      2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/chart/reference.py      2015-11-20 
09:48:05.000000000 +0100
@@ -3,6 +3,7 @@
 
 from itertools import chain
 
+from openpyxl.compat import unicode
 from openpyxl.descriptors.serialisable import Serialisable
 from openpyxl.descriptors import (
     MinMax,
@@ -62,18 +63,22 @@
 
 
     def __repr__(self):
-        fmt = "{0}!${1}${2}:${3}${4}"
+        return unicode(self)
+
+
+    def __str__(self):
+        fmt = u"{0}!${1}${2}:${3}${4}"
         if (self.min_col == self.max_col
             and self.min_row == self.max_row):
-            fmt = "{0}!${1}${2}"
+            fmt = u"{0}!${1}${2}"
         return fmt.format(self.sheetname,
                           get_column_letter(self.min_col), self.min_row,
                           get_column_letter(self.max_col), self.max_row
                           )
 
 
-    def __str__(self):
-        return repr(self)
+    __unicode__ = __str__
+
 
 
     def __len__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/chart/series_factory.py 
new/openpyxl-2.3.1/openpyxl/chart/series_factory.py
--- old/openpyxl-2.3.0/openpyxl/chart/series_factory.py 2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/chart/series_factory.py 2015-11-20 
09:48:05.000000000 +0100
@@ -17,7 +17,7 @@
 
     if title_from_data:
         cell = values.pop()
-        title = "{0}!{1}".format(values.sheetname, cell)
+        title = u"{0}!{1}".format(values.sheetname, cell)
         title = SeriesLabel(strRef=StrRef(title))
     elif title is not None:
         title = SeriesLabel(v=title)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/comments/author.py 
new/openpyxl-2.3.1/openpyxl/comments/author.py
--- old/openpyxl-2.3.0/openpyxl/comments/author.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/openpyxl-2.3.1/openpyxl/comments/author.py      2015-11-20 
09:48:05.000000000 +0100
@@ -0,0 +1,22 @@
+from __future__ import absolute_import
+# Copyright (c) 2010-2015 openpyxl
+from openpyxl.compat import unicode
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Sequence,
+    Alias
+)
+
+
+class AuthorList(Serialisable):
+
+    tagname = "authors"
+
+    author = Sequence(expected_type=unicode)
+    authors = Alias("author")
+
+    def __init__(self,
+                 author=(),
+                ):
+        self.author = author
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/comments/comments.py 
new/openpyxl-2.3.1/openpyxl/comments/comments.py
--- old/openpyxl-2.3.0/openpyxl/comments/comments.py    2015-10-20 
14:55:48.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/comments/comments.py    2015-11-20 
09:48:05.000000000 +0100
@@ -3,40 +3,14 @@
 
 
 class Comment(object):
-    __slots__ = ('_parent',
-                 '_text',
-                 '_author',
-                 '_width',
-                 '_height')
 
-    def __init__(self, text, author):
-        self._text = text
-        self._author = author
-        self._parent = None
-        self._width = '108pt'
-        self._height = '59.25pt'
-
-    @property
-    def author(self):
-        """ The name recorded for the author
+    _parent = None
 
-            :rtype: string
-        """
-        return self._author
-    @author.setter
-    def author(self, value):
-        self._author = value
-
-    @property
-    def text(self):
-        """ The text of the commment
-
-            :rtype: string
-        """
-        return self._text
-    @text.setter
-    def text(self, value):
-        self._text = value
+    def __init__(self, text, author):
+        self.content = text
+        self.author = author
+        self.width = '108pt'
+        self.height = '59.25pt'
 
     @property
     def parent(self):
@@ -47,3 +21,16 @@
         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))
         self._parent = cell
+
+
+    @property
+    def text(self):
+        """
+        Any comment text stripped of all formatting.
+        """
+        return self.content
+
+
+    @text.setter
+    def text(self, value):
+        self.content = value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/comments/properties.py 
new/openpyxl-2.3.1/openpyxl/comments/properties.py
--- old/openpyxl-2.3.0/openpyxl/comments/properties.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/openpyxl-2.3.1/openpyxl/comments/properties.py  2015-11-20 
09:48:05.000000000 +0100
@@ -0,0 +1,164 @@
+from __future__ import absolute_import
+# Copyright (c) 2010-2015 openpyxl
+
+## Incomplete!
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Float,
+    Integer,
+    Set,
+    String,
+    Bool,
+)
+from openpyxl.descriptors.excel import Guid, ExtensionList
+from openpyxl.descriptors.sequence import NestedSequence
+
+from openpyxl.xml.constants import SHEET_MAIN_NS
+
+from openpyxl.cell.text import Text
+from .author import AuthorList
+
+
+class ObjectAnchor(Serialisable):
+
+    moveWithCells = Bool(allow_none=True)
+    sizeWithCells = Bool(allow_none=True)
+    #z-order = Integer(allow_none=True) needs alias
+    #from
+    #to defs from xdr
+
+    def __init__(self,
+                 moveWithCells=None,
+                 sizeWithCells=None,
+                 #z-order=None,
+                ):
+        self.moveWithCells = moveWithCells
+        self.sizeWithCells = sizeWithCells
+        #self.z-order = z-order
+
+
+class Properties(Serialisable):
+
+    locked = Bool(allow_none=True)
+    defaultSize = Bool(allow_none=True)
+    _print = Bool(allow_none=True)
+    disabled = Bool(allow_none=True)
+    uiObject = Bool(allow_none=True)
+    autoFill = Bool(allow_none=True)
+    autoLine = Bool(allow_none=True)
+    altText = String(allow_none=True)
+    textHAlign = Set(values=(['left', 'center', 'right', 'justify', 
'distributed']))
+    textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 
'distributed']))
+    lockText = Bool(allow_none=True)
+    justLastX = Bool(allow_none=True)
+    autoScale = Bool(allow_none=True)
+    rowHidden = Bool(allow_none=True)
+    colHidden = Bool(allow_none=True)
+    anchor = Typed(expected_type=ObjectAnchor, )
+
+    __elements__ = ('anchor',)
+
+    def __init__(self,
+                 locked=None,
+                 defaultSize=None,
+                 _print=None,
+                 disabled=None,
+                 uiObject=None,
+                 autoFill=None,
+                 autoLine=None,
+                 altText=None,
+                 textHAlign=None,
+                 textVAlign=None,
+                 lockText=None,
+                 justLastX=None,
+                 autoScale=None,
+                 rowHidden=None,
+                 colHidden=None,
+                 anchor=None,
+                ):
+        self.locked = locked
+        self.defaultSize = defaultSize
+        self._print = _print
+        self.disabled = disabled
+        self.uiObject = uiObject
+        self.autoFill = autoFill
+        self.autoLine = autoLine
+        self.altText = altText
+        self.textHAlign = textHAlign
+        self.textVAlign = textVAlign
+        self.lockText = lockText
+        self.justLastX = justLastX
+        self.autoScale = autoScale
+        self.rowHidden = rowHidden
+        self.colHidden = colHidden
+        self.anchor = anchor
+
+
+
+class Comment(Serialisable):
+
+    tagname = "comment"
+
+    ref = String()
+    authorId = Integer()
+    guid = Guid(allow_none=True)
+    shapeId = Integer(allow_none=True)
+    text = Typed(expected_type=Text)
+    commentPr = Typed(expected_type=Properties, allow_none=True)
+    author = String(allow_none=True)
+
+    __elements__ = ('text', 'commentPr')
+
+    def __init__(self,
+                 ref="",
+                 authorId=0,
+                 guid=None,
+                 shapeId=0,
+                 text=None,
+                 commentPr=None,
+                 author=None,
+                ):
+        self.ref = ref
+        self.authorId = authorId
+        self.guid = guid
+        self.shapeId = shapeId
+        if text is None:
+            text = Text()
+        self.text = text
+        self.commentPr = commentPr
+        self.author = author
+
+
+    @property
+    def content(self):
+        """
+        Remove all inline formatting and stuff
+        """
+        return self.text.content
+
+
+class CommentSheet(Serialisable):
+
+    tagname = "comments"
+
+    authors = Typed(expected_type=AuthorList)
+    commentList = NestedSequence(expected_type=Comment, count=0)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('authors', 'commentList')
+
+    def __init__(self,
+                 authors=None,
+                 commentList=None,
+                 extLst=None,
+                ):
+        self.authors = authors
+        self.commentList = commentList
+
+
+    def to_tree(self):
+        tree = super(CommentSheet, self).to_tree()
+        tree.set("xmlns", SHEET_MAIN_NS)
+        return tree
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/comments/reader.py 
new/openpyxl-2.3.1/openpyxl/comments/reader.py
--- old/openpyxl-2.3.0/openpyxl/comments/reader.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/openpyxl-2.3.1/openpyxl/comments/reader.py      2015-11-20 
09:48:05.000000000 +0100
@@ -0,0 +1,48 @@
+from __future__ import absolute_import
+# Copyright (c) 2010-2015 openpyxl
+
+
+import os.path
+
+from openpyxl.comments import Comment
+from openpyxl.xml.constants import (
+    PACKAGE_WORKSHEET_RELS,
+    COMMENTS_NS,
+    PACKAGE_XL,
+    )
+from openpyxl.xml.functions import fromstring
+
+from .properties import CommentSheet
+
+
+def read_comments(ws, xml_source):
+    """Given a worksheet and the XML of its comments file, assigns comments to 
cells"""
+    root = fromstring(xml_source)
+    comments = CommentSheet.from_tree(root)
+    authors = comments.authors.author
+
+    for comment in comments.commentList:
+        author = authors[comment.authorId]
+        ref = comment.ref
+        comment = Comment(comment.content, author)
+
+        ws.cell(coordinate=ref).comment = comment
+
+
+def get_comments_file(worksheet_path, archive, valid_files):
+    """Returns the XML filename in the archive which contains the comments for
+    the spreadsheet with codename sheet_codename. Returns None if there is no
+    such file"""
+    sheet_codename = os.path.split(worksheet_path)[-1]
+    rels_file = PACKAGE_WORKSHEET_RELS + '/' + sheet_codename + '.rels'
+    if rels_file not in valid_files:
+        return None
+    rels_source = archive.read(rels_file)
+    root = fromstring(rels_source)
+    for i in root:
+        if i.attrib['Type'] == COMMENTS_NS:
+            comments_file = os.path.split(i.attrib['Target'])[-1]
+            comments_file = PACKAGE_XL + '/' + comments_file
+            if comments_file in valid_files:
+                return comments_file
+    return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/comments/writer.py 
new/openpyxl-2.3.1/openpyxl/comments/writer.py
--- old/openpyxl-2.3.0/openpyxl/comments/writer.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/openpyxl-2.3.1/openpyxl/comments/writer.py      2015-11-20 
09:48:05.000000000 +0100
@@ -0,0 +1,117 @@
+from __future__ import absolute_import
+# Copyright (c) 2010-2015 openpyxl
+
+
+from openpyxl.utils.indexed_list import IndexedList
+from openpyxl.compat import iteritems
+from openpyxl.xml.constants import SHEET_MAIN_NS
+from openpyxl.xml.functions import Element, SubElement, tostring
+from openpyxl.utils import (
+    column_index_from_string,
+    coordinate_from_string,
+)
+
+from .author import AuthorList
+from .properties import CommentSheet, Comment
+
+vmlns = "urn:schemas-microsoft-com:vml"
+officens = "urn:schemas-microsoft-com:office:office"
+excelns = "urn:schemas-microsoft-com:office:excel"
+
+
+class CommentWriter(object):
+
+
+    def __init__(self, sheet):
+        self.sheet = sheet
+        self.comments = []
+
+
+    def write_comments(self):
+        """
+        Create list of comments and authors
+        Sorted by row, col
+        """
+        # produce xml
+        authors = IndexedList()
+
+        for _coord, cell in sorted(self.sheet._cells.items()):
+            if cell.comment is not None:
+                comment = Comment(ref=cell.coordinate)
+                comment.authorId = authors.add(cell.comment.author)
+                comment.text.t = cell.comment.text
+                comment.height = cell.comment.height
+                comment.width = cell.comment.width
+                self.comments.append(comment)
+
+        author_list = AuthorList(authors)
+        root = CommentSheet(authors=author_list, commentList=self.comments)
+
+        return tostring(root.to_tree())
+
+    def write_comments_vml(self):
+        root = Element("xml")
+        shape_layout = SubElement(root, "{%s}shapelayout" % officens,
+                                  {"{%s}ext" % vmlns: "edit"})
+        SubElement(shape_layout,
+                   "{%s}idmap" % officens,
+                   {"{%s}ext" % vmlns: "edit", "data": "1"})
+        shape_type = SubElement(root,
+                                "{%s}shapetype" % vmlns,
+                                {"id": "_x0000_t202",
+                                 "coordsize": "21600,21600",
+                                 "{%s}spt" % officens: "202",
+                                 "path": "m,l,21600r21600,l21600,xe"})
+        SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
+        SubElement(shape_type,
+                   "{%s}path" % vmlns,
+                   {"gradientshapeok": "t",
+                    "{%s}connecttype" % officens: "rect"})
+
+        for idx, comment in enumerate(self.comments, 1026):
+
+            shape = _shape_factory()
+            col, row = coordinate_from_string(comment.ref)
+            row -= 1
+            column = column_index_from_string(col) - 1
+
+            shape.set('id',  "_x0000_s%04d" % idx)
+            client_data = shape.find("{%s}ClientData" % excelns)
+            client_data.find("{%s}Row" % excelns).text = str(row)
+            client_data.find("{%s}Column" % excelns).text = str(column)
+            root.append(shape)
+
+        return tostring(root)
+
+
+def _shape_factory():
+
+    style = ("position:absolute; margin-left:59.25pt;"
+             "margin-top:1.5pt;width:{width};height:{height};"
+             "z-index:1;visibility:hidden").format(height="59.25pt",
+                                               width="108pt")
+    attrs = {
+        "type": "#_x0000_t202",
+        "style": style,
+        "fillcolor": "#ffffe1",
+        "{%s}insetmode" % officens: "auto"
+    }
+    shape = Element("{%s}shape" % vmlns, attrs)
+
+    SubElement(shape, "{%s}fill" % vmlns,
+               {"color2": "#ffffe1"})
+    SubElement(shape, "{%s}shadow" % vmlns,
+               {"color": "black", "obscured": "t"})
+    SubElement(shape, "{%s}path" % vmlns,
+               {"{%s}connecttype" % officens: "none"})
+    textbox = SubElement(shape, "{%s}textbox" % vmlns,
+                         {"style": "mso-direction-alt:auto"})
+    SubElement(textbox, "div", {"style": "text-align:left"})
+    client_data = SubElement(shape, "{%s}ClientData" % excelns,
+                             {"ObjectType": "Note"})
+    SubElement(client_data, "{%s}MoveWithCells" % excelns)
+    SubElement(client_data, "{%s}SizeWithCells" % excelns)
+    SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
+    SubElement(client_data, "{%s}Row" % excelns)
+    SubElement(client_data, "{%s}Column" % excelns)
+    return shape
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/descriptors/excel.py 
new/openpyxl-2.3.1/openpyxl/descriptors/excel.py
--- old/openpyxl-2.3.0/openpyxl/descriptors/excel.py    2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/descriptors/excel.py    2015-11-20 
09:48:05.000000000 +0100
@@ -5,7 +5,6 @@
 Excel specific descriptors
 """
 
-from openpyxl.compat import basestring
 from openpyxl.xml.constants import REL_NS
 from . import MatchPattern, MinMax, Integer, String, Typed, Sequence
 from .serialisable import Serialisable
@@ -55,7 +54,7 @@
     ext = Sequence(expected_type=Extension)
 
     def __init__(self,
-                 ext=None,
+                 ext=(),
                 ):
         self.ext = ext
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/descriptors/sequence.py 
new/openpyxl-2.3.1/openpyxl/descriptors/sequence.py
--- old/openpyxl-2.3.0/openpyxl/descriptors/sequence.py 2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/descriptors/sequence.py 2015-11-20 
09:48:05.000000000 +0100
@@ -3,6 +3,7 @@
 
 from openpyxl.compat import safe_string
 from openpyxl.xml.functions import Element
+from openpyxl.utils.indexed_list import IndexedList
 
 from .base import Descriptor, _convert
 from .namespace import namespaced
@@ -17,12 +18,15 @@
     expected_type = type(None)
     seq_types = (list, tuple)
     idx_base = 0
+    unique = False
 
 
     def __set__(self, instance, seq):
         if not isinstance(seq, self.seq_types):
             raise TypeError("Value must be a sequence")
         seq = [_convert(self.expected_type, value) for value in seq]
+        if self.unique:
+            seq = IndexedList(seq)
 
         super(Sequence, self).__set__(instance, seq)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/drawing/fill.py 
new/openpyxl-2.3.1/openpyxl/drawing/fill.py
--- old/openpyxl-2.3.0/openpyxl/drawing/fill.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/drawing/fill.py 2015-11-20 09:48:05.000000000 
+0100
@@ -29,6 +29,7 @@
 class PatternFillProperties(Serialisable):
 
     tagname = "pattFill"
+    namespace = DRAWING_NS
 
     prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30', 
'pct40',
                         'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 
'horz', 'vert',
@@ -91,7 +92,7 @@
     fillRect = Typed(expected_type=RelativeRect, allow_none=True)
 
     def __init__(self,
-                 fillRect=None,
+                 fillRect=RelativeRect(),
                 ):
         self.fillRect = fillRect
 
@@ -336,7 +337,7 @@
                  rotWithShape=None,
                  blip=None,
                  tile=None,
-                 stretch=None,
+                 stretch=StretchInfoProperties(),
                  srcRect=None,
                 ):
         self.dpi = dpi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/formatting/rule.py 
new/openpyxl-2.3.1/openpyxl/formatting/rule.py
--- old/openpyxl-2.3.0/openpyxl/formatting/rule.py      2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/formatting/rule.py      2015-11-20 
09:48:05.000000000 +0100
@@ -9,18 +9,27 @@
     String,
     Sequence,
     Bool,
-    Float,
     NoneSet,
     Set,
-    Integer,)
-from openpyxl.descriptors.excel import HexBinary
+    Integer,
+    Float,
+)
+from openpyxl.descriptors.excel import HexBinary, ExtensionList
 from openpyxl.styles.colors import Color, ColorDescriptor
 from openpyxl.styles.differential import DifferentialStyle
 
 
-class ExtensionList(Serialisable):
-
-    pass
+class ValueDescriptor(Float):
+    """
+    Expected type depends upon type attribue of parent :-(
+    """
+
+    def __set__(self, instance, value):
+        if instance.type == "formula":
+            self.expected_type = basestring
+        else:
+            self.expected_type = float
+        super(ValueDescriptor, self).__set__(instance, value)
 
 
 class FormatObject(Serialisable):
@@ -28,10 +37,12 @@
     tagname = "cfvo"
 
     type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 
'percentile']))
-    val = Integer(allow_none=True)
+    val = ValueDescriptor(allow_none=True)
     gte = Bool(allow_none=True)
     extLst = Typed(expected_type=ExtensionList, allow_none=True)
 
+    __elements__ = ()
+
     def __init__(self,
                  type,
                  val=None,
@@ -41,7 +52,6 @@
         self.type = type
         self.val = val
         self.gte = gte
-        self.extLst = extLst
 
 
 class RuleType(Serialisable):
@@ -150,7 +160,7 @@
     extLst = Typed(expected_type=ExtensionList, allow_none=True)
     dxf = Typed(expected_type=DifferentialStyle, allow_none=True)
 
-    __elements__ = ('colorScale', 'dataBar', 'extLst', 'iconSet', 'formula')
+    __elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula')
 
     def __init__(self,
                  type,
@@ -190,7 +200,6 @@
         self.colorScale = colorScale
         self.dataBar = dataBar
         self.iconSet = iconSet
-        self.extLst = extLst
         self.dxf = dxf
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/packaging/manifest.py 
new/openpyxl-2.3.1/openpyxl/packaging/manifest.py
--- old/openpyxl-2.3.0/openpyxl/packaging/manifest.py   2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/packaging/manifest.py   2015-11-20 
09:48:05.000000000 +0100
@@ -41,6 +41,7 @@
 mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', 
".rels")
 mimetypes.add_type("application/vnd.ms-office.activeX", ".bin")
 mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", 
".vml")
+mimetypes.add_type("image/x-emf", ".emf")
 
 
 class FileExtension(Serialisable):
@@ -71,6 +72,10 @@
         self.ContentType = ContentType
 
 
+    def __hash__(self):
+        return hash((self.PartName, self.ContentType))
+
+
 DEFAULT_TYPES = [
     FileExtension("rels", 
"application/vnd.openxmlformats-package.relationships+xml"),
     FileExtension("xml", "application/xml"),
@@ -90,14 +95,14 @@
 
     tagname = "Types"
 
-    Default = Sequence(expected_type=FileExtension)
-    Override = Sequence(expected_type=Override)
+    Default = Sequence(expected_type=FileExtension, unique=True)
+    Override = Sequence(expected_type=Override, unique=True)
 
     __elements__ = ("Default", "Override")
 
     def __init__(self,
                  Default=(),
-                 Override=()
+                 Override=(),
                  ):
         if not Default:
             Default = DEFAULT_TYPES
@@ -132,15 +137,22 @@
         return tree
 
 
-def write_content_types(workbook, as_template=False):
+def write_content_types(workbook, as_template=False, exts=None):
 
-    seen = set()
     manifest = Manifest()
+
+    if exts is not None:
+        for ext in exts:
+            ext = os.path.splitext(ext)[-1]
+            mime = mimetypes.types_map[ext]
+            fe = FileExtension(ext[1:], mime)
+            if fe not in manifest.Default:
+                manifest.Default.append(fe)
+
     if workbook.vba_archive:
         node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES))
         manifest = Manifest.from_tree(node)
         del node
-        seen = set(manifest.filenames)
 
     # templates
     for part in manifest.Override:
@@ -159,21 +171,18 @@
     # worksheets
     for sheet_id, sheet in enumerate(workbook.worksheets):
         name = '/xl/worksheets/sheet%d.xml' % (sheet_id + 1)
-        if name not in seen:
-            manifest.Override.append(Override(name, WORKSHEET_TYPE))
+        manifest.Override.append(Override(name, WORKSHEET_TYPE))
 
         if sheet._charts or sheet._images:
             drawing_id += 1
             name = '/xl/drawings/drawing%d.xml' % drawing_id
-            if name not in seen:
-                manifest.Override.append(Override(name, DRAWING_TYPE))
+            manifest.Override.append(Override(name, DRAWING_TYPE))
 
 
             for chart in sheet._charts:
                 chart_id += 1
                 name = '/xl/charts/chart%d.xml' % chart_id
-                if name not in seen:
-                    manifest.Override.append(Override(name, CHART_TYPE))
+                manifest.Override.append(Override(name, CHART_TYPE))
 
         if sheet._comment_count > 0:
             comments_id += 1
@@ -181,27 +190,23 @@
             if vml not in manifest.Default:
                 manifest.Default.append(vml)
             name = '/xl/comments%d.xml' % comments_id
-            if name not in seen:
-                manifest.Override.append(Override(name, COMMENTS_TYPE))
+            manifest.Override.append(Override(name, COMMENTS_TYPE))
 
 
     # chartsheets
     for sheet_id, sheet in enumerate(workbook.chartsheets, sheet_id+1):
         name = '/xl/chartsheets/sheet%d.xml' % (sheet_id)
-        if name not in seen:
-            manifest.Override.append(Override(name, CHARTSHEET_TYPE))
+        manifest.Override.append(Override(name, CHARTSHEET_TYPE))
 
         if sheet._charts:
             drawing_id += 1
             name = '/xl/drawings/drawing%d.xml' % drawing_id
-            if name not in seen:
-                manifest.Override.append(Override(name, DRAWING_TYPE))
+            manifest.Override.append(Override(name, DRAWING_TYPE))
 
             for chart in sheet._charts:
                 chart_id += 1
                 name = '/xl/charts/chart%d.xml' % chart_id
-                if name not in seen:
-                    manifest.Override.append(Override(name, CHART_TYPE))
+                manifest.Override.append(Override(name, CHART_TYPE))
 
     #external links
     for idx, _ in enumerate(workbook._external_links, 1):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/reader/comments.py 
new/openpyxl-2.3.1/openpyxl/reader/comments.py
--- old/openpyxl-2.3.0/openpyxl/reader/comments.py      2015-10-20 
14:55:48.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/reader/comments.py      1970-01-01 
01:00:00.000000000 +0100
@@ -1,54 +0,0 @@
-from __future__ import absolute_import
-# Copyright (c) 2010-2015 openpyxl
-
-
-import os.path
-
-from openpyxl.comments import Comment
-from openpyxl.xml.constants import (
-    PACKAGE_WORKSHEET_RELS,
-    SHEET_MAIN_NS,
-    COMMENTS_NS,
-    PACKAGE_XL,
-    )
-from openpyxl.xml.functions import fromstring, safe_iterator
-
-def _get_author_list(root):
-    author_subtree = root.find('{%s}authors' % SHEET_MAIN_NS)
-    return [author.text for author in author_subtree]
-
-def read_comments(ws, xml_source):
-    """Given a worksheet and the XML of its comments file, assigns comments to 
cells"""
-    root = fromstring(xml_source)
-    authors = _get_author_list(root)
-    comment_nodes = safe_iterator(root, ('{%s}comment' % SHEET_MAIN_NS))
-    for node in comment_nodes:
-        author = authors[int(node.attrib['authorId'])]
-        cell = node.attrib['ref']
-        text_node = node.find('{%s}text' % SHEET_MAIN_NS)
-        substrs = []
-        for run in text_node.findall('{%s}r' % SHEET_MAIN_NS):
-            runtext = ''.join([t.text for t in run.findall('{%s}t' % 
SHEET_MAIN_NS)])
-            substrs.append(runtext)
-        comment_text = ''.join(substrs)
-
-        comment = Comment(comment_text, author)
-        ws.cell(coordinate=cell).comment = comment
-
-def get_comments_file(worksheet_path, archive, valid_files):
-    """Returns the XML filename in the archive which contains the comments for
-    the spreadsheet with codename sheet_codename. Returns None if there is no
-    such file"""
-    sheet_codename = os.path.split(worksheet_path)[-1]
-    rels_file = PACKAGE_WORKSHEET_RELS + '/' + sheet_codename + '.rels'
-    if rels_file not in valid_files:
-        return None
-    rels_source = archive.read(rels_file)
-    root = fromstring(rels_source)
-    for i in root:
-        if i.attrib['Type'] == COMMENTS_NS:
-            comments_file = os.path.split(i.attrib['Target'])[-1]
-            comments_file = PACKAGE_XL + '/' + comments_file
-            if comments_file in valid_files:
-                return comments_file
-    return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/reader/excel.py 
new/openpyxl-2.3.1/openpyxl/reader/excel.py
--- old/openpyxl-2.3.0/openpyxl/reader/excel.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/reader/excel.py 2015-11-20 09:48:05.000000000 
+0100
@@ -50,7 +50,7 @@
 from openpyxl.workbook.properties import read_properties, DocumentProperties
 from openpyxl.worksheet.read_only import ReadOnlyWorksheet
 from .worksheet import WorkSheetParser
-from .comments import read_comments, get_comments_file
+from openpyxl.comments.reader import read_comments, get_comments_file
 # Use exc_info for Python 2 compatibility with "except Exception[,/ as] e"
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/reader/strings.py 
new/openpyxl-2.3.1/openpyxl/reader/strings.py
--- old/openpyxl-2.3.0/openpyxl/reader/strings.py       2015-10-20 
14:55:48.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/reader/strings.py       2015-11-20 
09:48:05.000000000 +0100
@@ -1,44 +1,27 @@
 from __future__ import absolute_import
 # Copyright (c) 2010-2015 openpyxl
 
-"""Read the shared strings table."""
+from openpyxl.cell.text import Text
+from openpyxl.utils.indexed_list import IndexedList
 
-from openpyxl.compat import unicode
+from openpyxl.xml.functions import iterparse
+from openpyxl.xml.constants import SHEET_MAIN_NS
 
-# package imports
-from openpyxl.utils.indexed_list import IndexedList
-from openpyxl.xml.functions import fromstring, safe_iterator
-from openpyxl.xml.constants import SHEET_MAIN_NS, XML_NS
+from .worksheet import _get_xml_iter
 
 
 def read_string_table(xml_source):
     """Read in all shared strings in the table"""
-    root = fromstring(text=xml_source)
-    nodes = safe_iterator(root, '{%s}si' % SHEET_MAIN_NS)
-    strings = (get_string(node) for node in nodes)
-    return IndexedList(strings)
+    strings = []
+    src = _get_xml_iter(xml_source)
 
+    for _, node in iterparse(src):
+        if node.tag == '{%s}si' % SHEET_MAIN_NS:
 
-def get_string(string_index_node):
-    """Read the contents of a specific string index"""
-    rich_nodes = string_index_node.findall('{%s}r' % SHEET_MAIN_NS)
-    if rich_nodes:
-        reconstructed_text = []
-        for rich_node in rich_nodes:
-            partial_text = get_text(rich_node)
-            reconstructed_text.append(partial_text)
-        return unicode(''.join(reconstructed_text))
-    return get_text(string_index_node)
-
-
-def get_text(rich_node):
-    """Read rich text, discarding formatting if not disallowed"""
-    text_node = rich_node.find('{%s}t' % SHEET_MAIN_NS)
-    text = text_node.text or unicode('')
-
-    if text_node.get('{%s}space' % XML_NS) != 'preserve':
-        text = text.strip()
-
-    # fix XML escaping sequence for '_x'
-    text = text.replace('x005F_', '')
-    return unicode(text)
+            text = Text.from_tree(node).content
+            text = text.replace('x005F_', '')
+            strings.append(text)
+
+            node.clear()
+
+    return IndexedList(strings)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/reader/style.py 
new/openpyxl-2.3.1/openpyxl/reader/style.py
--- old/openpyxl-2.3.0/openpyxl/reader/style.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/reader/style.py 2015-11-20 09:48:05.000000000 
+0100
@@ -125,7 +125,7 @@
         """
         node = self.root.find("{%s}cellStyles" % SHEET_MAIN_NS)
         names = {}
-        for _name in node:
+        for _name in safe_iterator(node, '{%s}cellStyle' % SHEET_MAIN_NS):
             name = _name.get("name")
             style = NamedStyle(name=name,
                                builtinId=_name.get("builtinId"),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/reader/worksheet.py 
new/openpyxl-2.3.1/openpyxl/reader/worksheet.py
--- old/openpyxl-2.3.0/openpyxl/reader/worksheet.py     2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/reader/worksheet.py     2015-11-20 
09:48:05.000000000 +0100
@@ -245,7 +245,7 @@
         self.ws.print_options = PrintOptions.from_tree(element)
 
     def parse_margins(self, element):
-        self.page_margins = PageMargins.from_tree(element)
+        self.ws.page_margins = PageMargins.from_tree(element)
 
     def parse_page_setup(self, element):
         self.ws.page_setup = PrintPageSetup.from_tree(element)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/styles/fonts.py 
new/openpyxl-2.3.1/openpyxl/styles/fonts.py
--- old/openpyxl-2.3.0/openpyxl/styles/fonts.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/styles/fonts.py 2015-11-20 09:48:05.000000000 
+0100
@@ -35,8 +35,8 @@
 
     name = NestedString()
     charset = NestedInteger(allow_none=True)
-    family = NestedMinMax(min=0, max=14)
-    sz = NestedFloat()
+    family = NestedMinMax(min=0, max=14, allow_none=True)
+    sz = NestedFloat(allow_none=True)
     size = Alias("sz")
     b = NestedBool(to_tree=_no_value)
     bold = Alias("b")
@@ -52,7 +52,7 @@
                              'doubleAccounting'))
     underline = Alias("u")
     vertAlign = NestedNoneSet(values=('superscript', 'subscript', 'baseline'))
-    color = ColorDescriptor()
+    color = ColorDescriptor(allow_none=True)
     scheme = NestedNoneSet(values=("major", "minor"))
 
     tagname = "font"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/utils/__init__.py 
new/openpyxl-2.3.1/openpyxl/utils/__init__.py
--- old/openpyxl-2.3.0/openpyxl/utils/__init__.py       2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/utils/__init__.py       2015-11-20 
09:48:05.000000000 +0100
@@ -179,5 +179,5 @@
 
 def quote_sheetname(sheetname):
     if " " in sheetname:
-        sheetname = "'{0}'".format(sheetname)
+        sheetname = u"'{0}'".format(sheetname)
     return sheetname
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/utils/datetime.py 
new/openpyxl-2.3.1/openpyxl/utils/datetime.py
--- old/openpyxl-2.3.0/openpyxl/utils/datetime.py       2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/utils/datetime.py       2015-11-20 
09:48:05.000000000 +0100
@@ -70,8 +70,6 @@
         return datetime.datetime(*parts[:3] + [0])
 
 
-UTC = tzinfo(timedelta(0), offset=0)
-
 class GMT(tzinfo):
 
     def utcoffset(self, dt):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/worksheet/read_only.py 
new/openpyxl-2.3.1/openpyxl/worksheet/read_only.py
--- old/openpyxl-2.3.0/openpyxl/worksheet/read_only.py  2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/worksheet/read_only.py  2015-11-20 
09:48:05.000000000 +0100
@@ -186,7 +186,7 @@
     def columns(self):
         if self.max_column is None:
             self.calculate_dimension()
-        return super(IterableWorksheet, self).columns
+        return super(ReadOnlyWorksheet, self).columns
 
 
     def calculate_dimension(self, force=False):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/worksheet/worksheet.py 
new/openpyxl-2.3.1/openpyxl/worksheet/worksheet.py
--- old/openpyxl-2.3.0/openpyxl/worksheet/worksheet.py  2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/worksheet/worksheet.py  2015-11-20 
09:48:05.000000000 +0100
@@ -704,18 +704,19 @@
     @property
     def rows(self):
         """Iterate over all rows in the worksheet"""
+        if self.min_row == self.max_row == self.min_column == self.max_column:
+            return ((),)
         return tuple(self.iter_rows())
 
+
     @property
     def columns(self):
         """Iterate over all columns in the worksheet"""
-        max_row = self.max_row
-        min_row = 1
-        if not self._cells:
+        if self.min_row == self.max_row == self.min_column == self.max_column:
             return ((),)
         cols = []
         for col_idx in range(self.max_column):
-            cells = self.get_squared_range(col_idx + 1, min_row, col_idx + 1, 
max_row)
+            cells = self.get_squared_range(col_idx + 1, self.min_row, col_idx 
+ 1, self.max_row)
             col = chain.from_iterable(cells)
             cols.append(tuple(col))
         return tuple(cols)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/writer/comments.py 
new/openpyxl-2.3.1/openpyxl/writer/comments.py
--- old/openpyxl-2.3.0/openpyxl/writer/comments.py      2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/writer/comments.py      1970-01-01 
01:00:00.000000000 +0100
@@ -1,122 +0,0 @@
-from __future__ import absolute_import
-# Copyright (c) 2010-2015 openpyxl
-
-
-from openpyxl.utils.indexed_list import IndexedList
-from openpyxl.compat import iteritems
-from openpyxl.xml.constants import SHEET_MAIN_NS
-from openpyxl.xml.functions import Element, SubElement, tostring
-from openpyxl.utils import (
-    column_index_from_string,
-    coordinate_from_string,
-)
-
-vmlns = "urn:schemas-microsoft-com:vml"
-officens = "urn:schemas-microsoft-com:office:office"
-excelns = "urn:schemas-microsoft-com:office:excel"
-
-
-class CommentWriter(object):
-
-    def extract_comments(self):
-        """
-         extract list of comments and authors
-         """
-        for _coord, cell in iteritems(self.sheet._cells):
-            if cell.comment is not None:
-                self.authors.add(cell.comment.author)
-                self.comments.append(cell.comment)
-
-    def __init__(self, sheet):
-        self.sheet = sheet
-        self.authors = IndexedList()
-        self.comments = []
-
-        self.extract_comments()
-
-
-    def write_comments(self):
-        # produce xml
-        root = Element("{%s}comments" % SHEET_MAIN_NS)
-        authorlist_tag = SubElement(root, "{%s}authors" % SHEET_MAIN_NS)
-        for author in self.authors:
-            leaf = SubElement(authorlist_tag, "{%s}author" % SHEET_MAIN_NS)
-            leaf.text = author
-
-        commentlist_tag = SubElement(root, "{%s}commentList" % SHEET_MAIN_NS)
-        for comment in self.comments:
-            attrs = {'ref': comment._parent.coordinate,
-                     'authorId': '%d' % self.authors.index(comment.author),
-                     'shapeId': '0'}
-            comment_tag = SubElement(commentlist_tag,
-                                     "{%s}comment" % SHEET_MAIN_NS, attrs)
-
-            text_tag = SubElement(comment_tag, "{%s}text" % SHEET_MAIN_NS)
-            run_tag = SubElement(text_tag, "{%s}r" % SHEET_MAIN_NS)
-            SubElement(run_tag, "{%s}rPr" % SHEET_MAIN_NS)
-            t_tag = SubElement(run_tag, "{%s}t" % SHEET_MAIN_NS)
-            t_tag.text = comment.text
-
-        return tostring(root)
-
-    def write_comments_vml(self):
-        root = Element("xml")
-        shape_layout = SubElement(root, "{%s}shapelayout" % officens,
-                                  {"{%s}ext" % vmlns: "edit"})
-        SubElement(shape_layout,
-                   "{%s}idmap" % officens,
-                   {"{%s}ext" % vmlns: "edit", "data": "1"})
-        shape_type = SubElement(root,
-                                "{%s}shapetype" % vmlns,
-                                {"id": "_x0000_t202",
-                                 "coordsize": "21600,21600",
-                                 "{%s}spt" % officens: "202",
-                                 "path": "m,l,21600r21600,l21600,xe"})
-        SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
-        SubElement(shape_type,
-                   "{%s}path" % vmlns,
-                   {"gradientshapeok": "t",
-                    "{%s}connecttype" % officens: "rect"})
-
-        for i, comment in enumerate(self.comments, 1026):
-            shape = self._write_comment_shape(comment, i)
-            root.append(shape)
-
-        return tostring(root)
-
-    def _write_comment_shape(self, comment, idx):
-        # get zero-indexed coordinates of the comment
-        col, row = coordinate_from_string(comment._parent.coordinate)
-        row -= 1
-        column = column_index_from_string(col) - 1
-
-        style = ("position:absolute; margin-left:59.25pt;"
-                 "margin-top:1.5pt;width:%(width)s;height:%(height)s;"
-                 "z-index:1;visibility:hidden") % {'height': comment._height,
-                                                   'width': comment._width}
-        attrs = {
-            "id": "_x0000_s%04d" % idx ,
-            "type": "#_x0000_t202",
-            "style": style,
-            "fillcolor": "#ffffe1",
-            "{%s}insetmode" % officens: "auto"
-        }
-        shape = Element("{%s}shape" % vmlns, attrs)
-
-        SubElement(shape, "{%s}fill" % vmlns,
-                   {"color2": "#ffffe1"})
-        SubElement(shape, "{%s}shadow" % vmlns,
-                   {"color": "black", "obscured": "t"})
-        SubElement(shape, "{%s}path" % vmlns,
-                   {"{%s}connecttype" % officens: "none"})
-        textbox = SubElement(shape, "{%s}textbox" % vmlns,
-                             {"style": "mso-direction-alt:auto"})
-        SubElement(textbox, "div", {"style": "text-align:left"})
-        client_data = SubElement(shape, "{%s}ClientData" % excelns,
-                                 {"ObjectType": "Note"})
-        SubElement(client_data, "{%s}MoveWithCells" % excelns)
-        SubElement(client_data, "{%s}SizeWithCells" % excelns)
-        SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
-        SubElement(client_data, "{%s}Row" % excelns).text = "%d" % row
-        SubElement(client_data, "{%s}Column" % excelns).text = "%d" % column
-        return shape
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/writer/excel.py 
new/openpyxl-2.3.1/openpyxl/writer/excel.py
--- old/openpyxl-2.3.0/openpyxl/writer/excel.py 2015-10-20 18:25:39.000000000 
+0200
+++ new/openpyxl-2.3.1/openpyxl/writer/excel.py 2015-11-20 09:48:05.000000000 
+0100
@@ -45,7 +45,7 @@
     write_external_book_rel
 )
 
-from openpyxl.writer.comments import CommentWriter
+from openpyxl.comments.writer import CommentWriter
 
 ARC_VBA = ('xl/vba', r'xl/drawings/.*vmlDrawing\d\.vml', 'xl/ctrlProps', 
'customUI',
            'xl/activeX', r'xl/media/.*\.emf')
@@ -89,7 +89,12 @@
         self._write_string_table(archive)
         self._write_external_links(archive)
         archive.writestr(ARC_STYLE, self.style_writer.write_table())
-        manifest = write_content_types(self.workbook, as_template=as_template)
+
+        exts = []
+        for n in archive.namelist():
+            if "media" in n:
+                exts.append(n)
+        manifest = write_content_types(self.workbook, as_template=as_template, 
exts=exts)
         archive.writestr(ARC_CONTENT_TYPES, tostring(manifest.to_tree()))
 
     def _write_string_table(self, archive):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl/writer/write_only.py 
new/openpyxl-2.3.1/openpyxl/writer/write_only.py
--- old/openpyxl-2.3.0/openpyxl/writer/write_only.py    2015-10-20 
18:25:39.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl/writer/write_only.py    2015-11-20 
09:48:05.000000000 +0100
@@ -15,7 +15,7 @@
 
 from openpyxl.utils.exceptions import WorkbookAlreadySaved
 from openpyxl.writer.excel import ExcelWriter
-from openpyxl.writer.comments import CommentWriter
+from openpyxl.comments.writer import CommentWriter
 from .relations import write_rels
 from .worksheet import (
     write_autofilter,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl.egg-info/PKG-INFO 
new/openpyxl-2.3.1/openpyxl.egg-info/PKG-INFO
--- old/openpyxl-2.3.0/openpyxl.egg-info/PKG-INFO       2015-10-20 
18:26:07.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl.egg-info/PKG-INFO       2015-11-20 
09:49:39.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: openpyxl
-Version: 2.3.0
+Version: 2.3.1
 Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
 Home-page: http://openpyxl.readthedocs.org
 Author: See AUTHORS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openpyxl-2.3.0/openpyxl.egg-info/SOURCES.txt 
new/openpyxl-2.3.1/openpyxl.egg-info/SOURCES.txt
--- old/openpyxl-2.3.0/openpyxl.egg-info/SOURCES.txt    2015-10-20 
18:26:07.000000000 +0200
+++ new/openpyxl-2.3.1/openpyxl.egg-info/SOURCES.txt    2015-11-20 
09:49:39.000000000 +0100
@@ -16,6 +16,7 @@
 openpyxl/cell/cell.py
 openpyxl/cell/interface.py
 openpyxl/cell/read_only.py
+openpyxl/cell/text.py
 openpyxl/chart/_3d.py
 openpyxl/chart/__init__.py
 openpyxl/chart/_chart.py
@@ -55,7 +56,11 @@
 openpyxl/chartsheet/relation.py
 openpyxl/chartsheet/views.py
 openpyxl/comments/__init__.py
+openpyxl/comments/author.py
 openpyxl/comments/comments.py
+openpyxl/comments/properties.py
+openpyxl/comments/reader.py
+openpyxl/comments/writer.py
 openpyxl/compat/__init__.py
 openpyxl/compat/abc.py
 openpyxl/compat/functools.py
@@ -94,7 +99,6 @@
 openpyxl/packaging/manifest.py
 openpyxl/packaging/relationship.py
 openpyxl/reader/__init__.py
-openpyxl/reader/comments.py
 openpyxl/reader/excel.py
 openpyxl/reader/strings.py
 openpyxl/reader/style.py
@@ -147,7 +151,6 @@
 openpyxl/worksheet/views.py
 openpyxl/worksheet/worksheet.py
 openpyxl/writer/__init__.py
-openpyxl/writer/comments.py
 openpyxl/writer/dump_worksheet.py
 openpyxl/writer/etree_worksheet.py
 openpyxl/writer/excel.py


Reply via email to