Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions pyexcelerate/Range.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from . import DataTypes
import itertools
import re
import string
from collections import OrderedDict

import six
from . import Font, Fill, Format, Style
from six.moves import reduce
Expand All @@ -8,6 +12,25 @@
# to be immutable. Please don't modify attributes after instantiation. :)
#

# generate the list of columns name from "A" to "ZZZ" => mapping such that COORD2COLUMN[1] => "A"
COORD2COLUMN = (
# remove duplicates in collection by taking list(dict.fromkeys( collection ))
list(
OrderedDict.fromkeys(
# joind the items together so that ["","","A"] => "A", ["","R","Z"] => "RZ", ...
map(
"".join,
# build iterator with all combination of 3 items in the list ["", "A", "B", ..., "Z"]
itertools.product([""] + list(string.ascii_uppercase), repeat=3),
)
)
)
)
# reverse the previous mapping COORD2COLUMN to go from "A" to 1
COLUMN2COORD = {col: i for i, col in enumerate(COORD2COLUMN)}
# regexp that splits an excel reference (e.g. "B23") into row/col
RE_COLUMN_ROW = re.compile("([A-Z]+)(\d*)")


class Range(object):
A = ord("A")
Expand Down Expand Up @@ -246,30 +269,18 @@ def __setitem__(self, key, value):

@staticmethod
def string_to_coordinate(s):
# Convert a base-26 name to integer
y = 0
l = len(s)
for index, c in enumerate(s):
if ord(c) < Range.A or ord(c) > Range.Z:
s = s[index:]
break
y *= 26
y += ord(c) - Range.A + 1
if len(s) == l:
return y
# Convert a base-26 name to a coordinate (or integer if column)
col, num = RE_COLUMN_ROW.match(s).groups()
if num:
return (int(num), COLUMN2COORD[col])
else:
return (int(s), y)
return COLUMN2COORD[col]

@staticmethod
def coordinate_to_string(coord):
if coord[1] == float("inf"):
return "IV%s" % str(coord[0])

# convert an integer to base-26 name
y = coord[1] - 1
s = ""
while y >= 0:
d, m = divmod(y, 26)
s = chr(m + Range.A) + s
y = d - 1
return s + str(coord[0])
# Convert a coordinate to a base-26 name
row, col = coord
try:
return "%s%s" % (COORD2COLUMN[col], row)
except (IndexError, TypeError):
return "%s%s" % (COORD2COLUMN[256], row)
19 changes: 17 additions & 2 deletions pyexcelerate/tests/test_Range.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from nose.tools import eq_, raises

from ..Range import Range, COORD2COLUMN
from ..Workbook import Workbook
from ..Range import Range
from nose.tools import eq_, ok_, raises


def test_column_maps_generation():
# assert on some elements of the list of columns
assert COORD2COLUMN[1] == "A"
assert COORD2COLUMN[:6] == ["", "A", "B", "C", "D", "E"]
assert COORD2COLUMN[-6:] == ["ZZU", "ZZV", "ZZW", "ZZX", "ZZY", "ZZZ"]
# ensure the list is sorted (by comparing the left pad string ('A' -> ' A', 'BZ' -> ' BZ', ...)
cols_padded = [f"{col: >3}" for col in COORD2COLUMN]
assert sorted(cols_padded) == cols_padded, cols_padded
# ensure uniqueness of elements
assert len(set(COORD2COLUMN)) == len(COORD2COLUMN)


def test__string_to_coordinate():
Expand All @@ -14,6 +27,7 @@ def test__string_to_coordinate():
eq_(stc("B39"), (39, 2))
eq_(stc("AA1"), (1, 27))
eq_(stc("AB1"), (1, 28))
eq_(stc("XFD"), 16384)


def test__coordinate_to_string():
Expand All @@ -28,6 +42,7 @@ def test__coordinate_to_string():
eq_(cts((39, 2)), "B39")
eq_(cts((1, 27)), "AA1")
eq_(cts((1, 28)), "AB1")
eq_(cts((1, float("inf"))), "IV1")


def test_merge():
Expand Down