@@ -1838,18 +1838,29 @@ def __call__(self, declarations_str, inherited=None):
1838
1838
em_pt = float (em_pt [:- 2 ])
1839
1839
else :
1840
1840
em_pt = None
1841
- font_size = self . font_size_to_pt ( props ['font-size' ], em_pt )
1842
- if font_size == int ( font_size ):
1843
- size_fmt = '%d'
1844
- else :
1845
- size_fmt = '%f'
1846
- props [ 'font-size' ] = ( size_fmt + 'pt' ) % font_size
1841
+ props ['font-size' ] = self . size_to_pt (
1842
+ props [ 'font-size' ], em_pt , conversions = self . FONT_SIZE_RATIOS )
1843
+
1844
+ font_size = float ( props [ 'font-size' ][: - 2 ])
1845
+ else :
1846
+ font_size = None
1847
1847
1848
1848
# 3. TODO: resolve other font-relative units
1849
- # 4. TODO: resolve other relative styles (e.g. ?)
1849
+ for side in self .SIDES :
1850
+ prop = 'border-%s-width' % side
1851
+ if prop in props :
1852
+ props [prop ] = self .size_to_pt (
1853
+ props [prop ], em_pt = font_size , conversions = self .BORDER_WIDTH_RATIOS )
1854
+ for prop in ['margin-%s' % side , 'padding-%s' % side ]:
1855
+ if prop in props :
1856
+ # TODO: support %
1857
+ props [prop ] = self .size_to_pt (
1858
+ props [prop ], em_pt = font_size ,
1859
+ conversions = self .MARGIN_RATIOS )
1860
+
1850
1861
return props
1851
1862
1852
- UNIT_CONVERSIONS = {
1863
+ UNIT_RATIOS = {
1853
1864
'rem' : ('pt' , 12 ),
1854
1865
'ex' : ('em' , .5 ),
1855
1866
# 'ch':
@@ -1859,11 +1870,12 @@ def __call__(self, declarations_str, inherited=None):
1859
1870
'cm' : ('in' , 1 / 2.54 ),
1860
1871
'mm' : ('in' , 1 / 25.4 ),
1861
1872
'q' : ('mm' , .25 ),
1873
+ '!!default' : ('em' , 0 ),
1862
1874
}
1863
1875
1864
- FONT_SIZE_CONVERSIONS = UNIT_CONVERSIONS .copy ()
1865
- FONT_SIZE_CONVERSIONS .update ({
1866
- '%' : ('em' , 1 ),
1876
+ FONT_SIZE_RATIOS = UNIT_RATIOS .copy ()
1877
+ FONT_SIZE_RATIOS .update ({
1878
+ '%' : ('em' , .01 ),
1867
1879
'xx-small' : ('rem' , .5 ),
1868
1880
'x-small' : ('rem' , .625 ),
1869
1881
'small' : ('rem' , .8 ),
@@ -1873,11 +1885,26 @@ def __call__(self, declarations_str, inherited=None):
1873
1885
'xx-large' : ('rem' , 2 ),
1874
1886
'smaller' : ('em' , 1 / 1.2 ),
1875
1887
'larger' : ('em' , 1.2 ),
1888
+ '!!default' : ('em' , 1 ),
1889
+ })
1890
+
1891
+ MARGIN_RATIOS = UNIT_RATIOS .copy ()
1892
+ MARGIN_RATIOS .update ({
1893
+ 'none' : ('pt' , 0 ),
1876
1894
})
1877
1895
1878
- def font_size_to_pt (self , val , em_pt = None ):
1896
+ BORDER_WIDTH_RATIOS = UNIT_RATIOS .copy ()
1897
+ BORDER_WIDTH_RATIOS .update ({
1898
+ 'none' : ('pt' , 0 ),
1899
+ 'thick' : ('px' , 4 ),
1900
+ 'medium' : ('px' , 2 ),
1901
+ 'thin' : ('px' , 1 ),
1902
+ # Default: medium only if solid
1903
+ })
1904
+
1905
+ def size_to_pt (self , val , em_pt = None , conversions = UNIT_RATIOS ):
1879
1906
try :
1880
- val , unit = re .match ('(.*?)([a-zA-Z%].*)' , val ).groups ()
1907
+ val , unit = re .match ('(.*?)([a-zA-Z%! ].*)' , val ).groups ()
1881
1908
except AttributeError :
1882
1909
warnings .warn ('Unhandled font size: %r' % val , CSSWarning )
1883
1910
return
@@ -1900,9 +1927,19 @@ def font_size_to_pt(self, val, em_pt=None):
1900
1927
unit = 'pt'
1901
1928
continue
1902
1929
1903
- unit , mul = self .FONT_SIZE_CONVERSIONS [unit ]
1930
+ try :
1931
+ unit , mul = conversions [unit ]
1932
+ except KeyError :
1933
+ warnings .warn ('Unknown size unit: %r' % unit , CSSWarning )
1934
+ return self .size_to_pt ('1!!default' , conversions = conversions )
1904
1935
val *= mul
1905
- return val
1936
+
1937
+ val = round (val , 5 )
1938
+ if int (val ) == val :
1939
+ size_fmt = '%d'
1940
+ else :
1941
+ size_fmt = '%f'
1942
+ return (size_fmt + 'pt' ) % val
1906
1943
1907
1944
def atomize (self , declarations ):
1908
1945
for prop , value in declarations :
@@ -1915,33 +1952,33 @@ def atomize(self, declarations):
1915
1952
for prop , value in expand (prop , value ):
1916
1953
yield prop , value
1917
1954
1918
- DIRECTION_SHORTHANDS = {
1955
+ SIDE_SHORTHANDS = {
1919
1956
1 : [0 , 0 , 0 , 0 ],
1920
1957
2 : [0 , 1 , 0 , 1 ],
1921
1958
3 : [0 , 1 , 2 , 1 ],
1922
1959
4 : [0 , 1 , 2 , 3 ],
1923
1960
}
1924
- DIRECTIONS = ('top' , 'right' , 'bottom' , 'left' )
1961
+ SIDES = ('top' , 'right' , 'bottom' , 'left' )
1925
1962
1926
- def _direction_expander (prop_fmt ):
1963
+ def _side_expander (prop_fmt ):
1927
1964
def expand (self , prop , value ):
1928
1965
tokens = value .split ()
1929
1966
try :
1930
- mapping = self .DIRECTION_SHORTHANDS [len (tokens )]
1967
+ mapping = self .SIDE_SHORTHANDS [len (tokens )]
1931
1968
except KeyError :
1932
1969
warnings .warn ('Could not expand "%s: %s"' % (prop , value ),
1933
1970
CSSWarning )
1934
1971
return
1935
- for key , idx in zip (self .DIRECTIONS , mapping ):
1972
+ for key , idx in zip (self .SIDES , mapping ):
1936
1973
yield prop_fmt % key , tokens [idx ]
1937
1974
1938
1975
return expand
1939
1976
1940
- expand_border_color = _direction_expander ('border-%s-color' )
1941
- expand_border_style = _direction_expander ('border-%s-style' )
1942
- expand_border_width = _direction_expander ('border-%s-width' )
1943
- expand_margin = _direction_expander ('margin-%s' )
1944
- expand_padding = _direction_expander ('padding-%s' )
1977
+ expand_border_color = _side_expander ('border-%s-color' )
1978
+ expand_border_style = _side_expander ('border-%s-style' )
1979
+ expand_border_width = _side_expander ('border-%s-width' )
1980
+ expand_margin = _side_expander ('margin-%s' )
1981
+ expand_padding = _side_expander ('padding-%s' )
1945
1982
1946
1983
def parse (self , declarations_str ):
1947
1984
"""Generates (prop, value) pairs from declarations
@@ -1999,6 +2036,12 @@ def __call__(self, declarations_str):
1999
2036
declarations_str : str
2000
2037
List of CSS declarations.
2001
2038
e.g. "font-weight: bold; background: blue"
2039
+
2040
+ Returns
2041
+ -------
2042
+ xlstyle : dict
2043
+ A style as interpreted by ExcelWriter when found in
2044
+ ExcelCell.style.
2002
2045
"""
2003
2046
# TODO: memoize?
2004
2047
properties = self .compute_css (declarations_str , self .inherited )
@@ -2045,28 +2088,65 @@ def build_alignment(self, props):
2045
2088
}
2046
2089
2047
2090
def build_border (self , props ):
2091
+ print (props )
2048
2092
return {side : {
2049
- # TODO: convert styles and widths to openxml, one of:
2050
- # 'dashDot'
2051
- # 'dashDotDot'
2052
- # 'dashed'
2053
- # 'dotted'
2054
- # 'double'
2055
- # 'hair'
2056
- # 'medium'
2057
- # 'mediumDashDot'
2058
- # 'mediumDashDotDot'
2059
- # 'mediumDashed'
2060
- # 'slantDashDot'
2061
- # 'thick'
2062
- # 'thin'
2063
- 'style' : ('medium'
2064
- if props .get ('border-%s-style' % side ) == 'solid'
2065
- else None ),
2093
+ 'style' : self ._border_style (props .get ('border-%s-style' % side ),
2094
+ props .get ('border-%s-width' % side )),
2066
2095
'color' : self .color_to_excel (
2067
2096
props .get ('border-%s-color' % side )),
2068
2097
} for side in ['top' , 'right' , 'bottom' , 'left' ]}
2069
2098
2099
+ def _border_style (self , style , width ):
2100
+ # TODO: convert styles and widths to openxml, one of:
2101
+ # 'dashDot'
2102
+ # 'dashDotDot'
2103
+ # 'dashed'
2104
+ # 'dotted'
2105
+ # 'double'
2106
+ # 'hair'
2107
+ # 'medium'
2108
+ # 'mediumDashDot'
2109
+ # 'mediumDashDotDot'
2110
+ # 'mediumDashed'
2111
+ # 'slantDashDot'
2112
+ # 'thick'
2113
+ # 'thin'
2114
+ if width is None and style is None :
2115
+ return None
2116
+ if style == 'none' or style == 'hidden' :
2117
+ return None
2118
+
2119
+ if width is None :
2120
+ width = '2pt'
2121
+ width = float (width [:- 2 ])
2122
+ if width < 1e-5 :
2123
+ return None
2124
+ if width < 1 :
2125
+ width_name = 'hair'
2126
+ elif width < 2 :
2127
+ width_name = 'thin'
2128
+ elif width < 3.5 :
2129
+ width_name = 'medium'
2130
+ else :
2131
+ width_name = 'thick'
2132
+
2133
+ if style in (None , 'groove' , 'ridge' , 'inset' , 'outset' ):
2134
+ # not handled
2135
+ style = 'solid'
2136
+
2137
+ if style == 'double' :
2138
+ return 'double'
2139
+ if style == 'solid' :
2140
+ return width_name
2141
+ if style == 'dotted' :
2142
+ if width_name in ('hair' , 'thin' ):
2143
+ return 'dotted'
2144
+ return 'mediumDashDotDot'
2145
+ if style == 'dashed' :
2146
+ if width_name in ('hair' , 'thin' ):
2147
+ return 'dashed'
2148
+ return 'mediumDashed'
2149
+
2070
2150
def build_fill (self , props ):
2071
2151
# TODO: perhaps allow for special properties
2072
2152
# -excel-pattern-bgcolor and -excel-pattern-type
@@ -2087,9 +2167,11 @@ def build_font(self, props):
2087
2167
size = props .get ('font-size' )
2088
2168
if size is not None :
2089
2169
assert size .endswith ('pt' )
2090
- size = int ( round ( size [:- 2 ]) )
2170
+ size = float ( size [:- 2 ])
2091
2171
2092
- font_names = props .get ('font-family' , '' ).split ()
2172
+ font_names = [name .strip ()
2173
+ for name in props .get ('font-family' , '' ).split (',' )
2174
+ if name .strip ()]
2093
2175
family = None
2094
2176
for name in font_names :
2095
2177
if name == 'serif' :
@@ -2155,9 +2237,9 @@ def color_to_excel(self, val):
2155
2237
if val is None :
2156
2238
return None
2157
2239
if val .startswith ('#' ) and len (val ) == 7 :
2158
- return val [1 :]
2240
+ return val [1 :]. upper ()
2159
2241
if val .startswith ('#' ) and len (val ) == 4 :
2160
- return val [1 ] * 2 + val [2 ] * 2 + val [3 ] * 2
2242
+ return ( val [1 ] * 2 + val [2 ] * 2 + val [3 ] * 2 ). upper ()
2161
2243
try :
2162
2244
return self .NAMED_COLORS [val ]
2163
2245
except KeyError :
0 commit comments