Skip to content

Commit f62f02d

Browse files
committed
File restructure
1 parent dc953d4 commit f62f02d

File tree

6 files changed

+1109
-1080
lines changed

6 files changed

+1109
-1080
lines changed

pandas/formats/common.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
def _get_level_lengths(levels, sentinel=''):
2+
"""For each index in each level the function returns lengths of indexes.
3+
4+
Parameters
5+
----------
6+
levels : list of lists
7+
List of values on for level.
8+
sentinel : string, optional
9+
Value which states that no new index starts on there.
10+
11+
Returns
12+
----------
13+
Returns list of maps. For each level returns map of indexes (key is index
14+
in row and value is length of index).
15+
"""
16+
if len(levels) == 0:
17+
return []
18+
19+
control = [True for x in levels[0]]
20+
21+
result = []
22+
for level in levels:
23+
last_index = 0
24+
25+
lengths = {}
26+
for i, key in enumerate(level):
27+
if control[i] and key == sentinel:
28+
pass
29+
else:
30+
control[i] = False
31+
lengths[last_index] = i - last_index
32+
last_index = i
33+
34+
lengths[last_index] = len(level) - last_index
35+
36+
result.append(lengths)
37+
38+
return result
39+
40+

pandas/formats/css.py

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""Utilities for interpreting CSS from Stylers for formatting non-HTML outputs
2+
"""
3+
4+
import re
5+
import warnings
6+
7+
8+
class CSSWarning(UserWarning):
9+
"""This CSS syntax cannot currently be parsed"""
10+
pass
11+
12+
13+
class CSSResolver(object):
14+
"""A callable for parsing and resolving CSS to atomic properties
15+
16+
"""
17+
18+
INITIAL_STYLE = {
19+
}
20+
21+
def __call__(self, declarations_str, inherited=None):
22+
""" the given declarations to atomic properties
23+
24+
Parameters
25+
----------
26+
declarations_str : str
27+
A list of CSS declarations
28+
inherited : dict, optional
29+
Atomic properties indicating the inherited style context in which
30+
declarations_str is to be resolved. ``inherited`` should already
31+
be resolved, i.e. valid output of this method.
32+
33+
Returns
34+
-------
35+
props : dict
36+
Atomic CSS 2.2 properties
37+
38+
Examples
39+
--------
40+
>>> resolve = CSSResolver()
41+
>>> inherited = {'font-family': 'serif', 'font-weight': 'bold'}
42+
>>> out = resolve('''
43+
... border-color: BLUE RED;
44+
... font-size: 1em;
45+
... font-size: 2em;
46+
... font-weight: normal;
47+
... font-weight: inherit;
48+
... ''', inherited)
49+
>>> sorted(out.items()) # doctest: +NORMALIZE_WHITESPACE
50+
[('border-bottom-color', 'blue'),
51+
('border-left-color', 'red'),
52+
('border-right-color', 'red'),
53+
('border-top-color', 'blue'),
54+
('font-family', 'serif'),
55+
('font-size', '24pt'),
56+
('font-weight', 'bold')]
57+
"""
58+
59+
props = dict(self.atomize(self.parse(declarations_str)))
60+
if inherited is None:
61+
inherited = {}
62+
63+
# 1. resolve inherited, initial
64+
for prop, val in inherited.items():
65+
if prop not in props:
66+
props[prop] = val
67+
68+
for prop, val in list(props.items()):
69+
if val == 'inherit':
70+
val = inherited.get(prop, 'initial')
71+
if val == 'initial':
72+
val = self.INITIAL_STYLE.get(prop)
73+
74+
if val is None:
75+
# we do not define a complete initial stylesheet
76+
del props[prop]
77+
else:
78+
props[prop] = val
79+
80+
# 2. resolve relative font size
81+
if props.get('font-size'):
82+
if 'font-size' in inherited:
83+
em_pt = inherited['font-size']
84+
assert em_pt[-2:] == 'pt'
85+
em_pt = float(em_pt[:-2])
86+
else:
87+
em_pt = None
88+
props['font-size'] = self.size_to_pt(
89+
props['font-size'], em_pt, conversions=self.FONT_SIZE_RATIOS)
90+
91+
font_size = float(props['font-size'][:-2])
92+
else:
93+
font_size = None
94+
95+
# 3. TODO: resolve other font-relative units
96+
for side in self.SIDES:
97+
prop = 'border-%s-width' % side
98+
if prop in props:
99+
props[prop] = self.size_to_pt(
100+
props[prop], em_pt=font_size,
101+
conversions=self.BORDER_WIDTH_RATIOS)
102+
for prop in ['margin-%s' % side, 'padding-%s' % side]:
103+
if prop in props:
104+
# TODO: support %
105+
props[prop] = self.size_to_pt(
106+
props[prop], em_pt=font_size,
107+
conversions=self.MARGIN_RATIOS)
108+
109+
return props
110+
111+
UNIT_RATIOS = {
112+
'rem': ('pt', 12),
113+
'ex': ('em', .5),
114+
# 'ch':
115+
'px': ('pt', .75),
116+
'pc': ('pt', 12),
117+
'in': ('pt', 72),
118+
'cm': ('in', 1 / 2.54),
119+
'mm': ('in', 1 / 25.4),
120+
'q': ('mm', .25),
121+
'!!default': ('em', 0),
122+
}
123+
124+
FONT_SIZE_RATIOS = UNIT_RATIOS.copy()
125+
FONT_SIZE_RATIOS.update({
126+
'%': ('em', .01),
127+
'xx-small': ('rem', .5),
128+
'x-small': ('rem', .625),
129+
'small': ('rem', .8),
130+
'medium': ('rem', 1),
131+
'large': ('rem', 1.125),
132+
'x-large': ('rem', 1.5),
133+
'xx-large': ('rem', 2),
134+
'smaller': ('em', 1 / 1.2),
135+
'larger': ('em', 1.2),
136+
'!!default': ('em', 1),
137+
})
138+
139+
MARGIN_RATIOS = UNIT_RATIOS.copy()
140+
MARGIN_RATIOS.update({
141+
'none': ('pt', 0),
142+
})
143+
144+
BORDER_WIDTH_RATIOS = UNIT_RATIOS.copy()
145+
BORDER_WIDTH_RATIOS.update({
146+
'none': ('pt', 0),
147+
'thick': ('px', 4),
148+
'medium': ('px', 2),
149+
'thin': ('px', 1),
150+
# Default: medium only if solid
151+
})
152+
153+
def size_to_pt(self, val, em_pt=None, conversions=UNIT_RATIOS):
154+
try:
155+
val, unit = re.match('(.*?)([a-zA-Z%!].*)', val).groups()
156+
except AttributeError:
157+
warnings.warn('Unhandled font size: %r' % val, CSSWarning)
158+
return
159+
if val == '':
160+
# hack for 'large' etc.
161+
val = 1
162+
else:
163+
try:
164+
val = float(val)
165+
except ValueError:
166+
warnings.warn('Unhandled font size: %r' % val + unit,
167+
CSSWarning)
168+
169+
while unit != 'pt':
170+
if unit == 'em':
171+
if em_pt is None:
172+
unit = 'rem'
173+
else:
174+
val *= em_pt
175+
unit = 'pt'
176+
continue
177+
178+
try:
179+
unit, mul = conversions[unit]
180+
except KeyError:
181+
warnings.warn('Unknown size unit: %r' % unit, CSSWarning)
182+
return self.size_to_pt('1!!default', conversions=conversions)
183+
val *= mul
184+
185+
val = round(val, 5)
186+
if int(val) == val:
187+
size_fmt = '%d'
188+
else:
189+
size_fmt = '%f'
190+
return (size_fmt + 'pt') % val
191+
192+
def atomize(self, declarations):
193+
for prop, value in declarations:
194+
attr = 'expand_' + prop.replace('-', '_')
195+
try:
196+
expand = getattr(self, attr)
197+
except AttributeError:
198+
yield prop, value
199+
else:
200+
for prop, value in expand(prop, value):
201+
yield prop, value
202+
203+
SIDE_SHORTHANDS = {
204+
1: [0, 0, 0, 0],
205+
2: [0, 1, 0, 1],
206+
3: [0, 1, 2, 1],
207+
4: [0, 1, 2, 3],
208+
}
209+
SIDES = ('top', 'right', 'bottom', 'left')
210+
211+
def _side_expander(prop_fmt):
212+
def expand(self, prop, value):
213+
tokens = value.split()
214+
try:
215+
mapping = self.SIDE_SHORTHANDS[len(tokens)]
216+
except KeyError:
217+
warnings.warn('Could not expand "%s: %s"' % (prop, value),
218+
CSSWarning)
219+
return
220+
for key, idx in zip(self.SIDES, mapping):
221+
yield prop_fmt % key, tokens[idx]
222+
223+
return expand
224+
225+
expand_border_color = _side_expander('border-%s-color')
226+
expand_border_style = _side_expander('border-%s-style')
227+
expand_border_width = _side_expander('border-%s-width')
228+
expand_margin = _side_expander('margin-%s')
229+
expand_padding = _side_expander('padding-%s')
230+
231+
def parse(self, declarations_str):
232+
"""Generates (prop, value) pairs from declarations
233+
234+
In a future version may generate parsed tokens from tinycss/tinycss2
235+
"""
236+
for decl in declarations_str.split(';'):
237+
if not decl.strip():
238+
continue
239+
prop, sep, val = decl.partition(':')
240+
prop = prop.strip().lower()
241+
# TODO: don't lowercase case sensitive parts of values (strings)
242+
val = val.strip().lower()
243+
if not sep:
244+
warnings.warn('Ill-formatted attribute: expected a colon '
245+
'in %r' % decl, CSSWarning)
246+
yield prop, val

0 commit comments

Comments
 (0)