| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- #!/usr/bin/env python
- from block import Block
- def split_selectors(raw_selector):
- """
- Split a selector with commas and arbitrary whitespaces into a list of
- selector swith single-space whitespaces.
- """
- return [' '.join(s.split()) for s in raw_selector.split(',')]
- class StyleSheet(object):
- def __init__(self, css=None):
- # List of encountered blocks
- self.blocks = []
- # Map of property stringification to list of containing blocks
- self.property_locations = {}
- if css:
- self.parse_css(css)
- def parse_css(self, css):
- """
- Parse CSS code one character at a time. This is more efficient than
- simply splitting on brackets, especially for large style sheets. All
- comments are ignored (both inline and multiline).
- """
- stack = char = ''
- prev = block = None
- lineno = 1
- multiline_comment = inline_comment = False
- try:
- for c in css:
- char = c
- if multiline_comment:
- # Multiline comment end?
- if c == '/' and prev == '*':
- multiline_comment = False
- elif inline_comment:
- # Inline comment end?
- if c == '\n':
- inline_comment = False
- elif c == '{':
- # Block start
- selectors = split_selectors(stack)
- stack = ''
- assert len(selectors)
- block = Block(*selectors)
- elif c == '}':
- # Block end
- assert block is not None
- block = None
- elif c == ';':
- # Property definition
- assert block is not None
- name, value = map(str.strip, stack.split(':', 1))
- assert '\n' not in name
- block.add_property(name, value)
- elif c == '*' and prev == '/':
- # Multiline comment start
- multiline_comment = True
- elif c == '/' and prev == '/':
- # Inline comment start
- inline_comment = True
- else:
- if c == '\n':
- lineno += 1
- stack += c
- prev = c
- except AssertionError:
- raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
- def generate_css(self, compress_whitespace=False, compress_color=False,
- compress_font=False, compress_dimension=False,
- compress_blocks=False):
- """
- Generate CSS code for the entire stylesheet.
- Options:
- compress_whitespace | Omit unnecessary whitespaces and semicolons.
- compress_color | Replace color codes/names with shorter synonyms.
- compress_font | Replace separate font statements with shortcut
- | font statement where possible.
- compress_dimension | Replace separate margin/padding statements with
- | shortcut statements where possible.
- compress_blocks | Combine or split blocks into blocks with
- | comma-separated selectors if it results in less
- | CSS code.
- """
- blocks = self.blocks
- if compress_blocks:
- blocks = self._compress_blocks()
- options = dict(compress_whitespace=compress_whitespace,
- compress_color=compress_color,
- compress_font=compress_font,
- compress_dimension=compress_dimension)
- newline = '' if compress_whitespace else '\n'
- return newline.join(block.generate_css(**options)
- for block in self.blocks)
- def _compress_blocks(self):
- pass
- #for block in self.blocks
- def compress(self, **kwargs):
- """
- Shortcut for `generate_css`, with all compression options enabled by
- default. Keyword argument names are preceded by 'compress_' before
- being passed to `generate_css`.
- """
- options = dict(whitespace=True, color=True, font=True, dimension=True,
- blocks=True)
- options.update(kwargs)
- options = dict([('compress_' + k, v) for k, v in options.items()])
- return self.generate_css(**options)
- def __str__(self):
- return self.generate_css()
- if __name__ == '__main__': # pragma: nocover
- # TODO: Command-line options parser
- pass
|