def test_parentRule(self):
        "CSSStyleDeclaration.parentRule"
        s = css_parser.css.CSSStyleDeclaration()
        sheet = css_parser.css.CSSStyleRule()
        s.parentRule = sheet
        self.assertEqual(sheet, s.parentRule)

        sheet = css_parser.parseString('a{x:1}')
        s = sheet.cssRules[0]
        d = s.style
        self.assertEqual(s, d.parentRule)

        s = css_parser.parseString('''
        @font-face {
            font-weight: bold;
            }
        a {
            font-weight: bolder;
            }
        @page {
            font-weight: bolder;
            }
        ''')
        for r in s:
            self.assertEqual(r.style.parentRule, r)
Example #2
0
    def test_handlers(self):
        "css_parser.log"
        s = self._setHandler()

        css_parser.log.setLevel(logging.FATAL)
        self.assertEqual(css_parser.log.getEffectiveLevel(), logging.FATAL)

        css_parser.parseString('a { color: 1 }')
        self.assertEqual(s.getvalue(), '')

        css_parser.log.setLevel(logging.DEBUG)
        css_parser.parseString('a { color: 1 }')
        self.assertEqual(
            s.getvalue(),
            'ERROR    Property: Invalid value for "CSS Level 2.1" property: 1 [1:5: color]\n'
        )

        s = self._setHandler()

        css_parser.log.setLevel(logging.WARNING)
        css_parser.parseUrl('http://example.com')
        q = s.getvalue()[:38]
        if q.startswith('WARNING    URLError'):
            pass
        else:
            self.assertEqual(q, 'ERROR    Expected "text/css" mime type')
Example #3
0
 def test_comments(self):
     "PropertyValue with comment"
     # issue #45
     for t in (
             'green',
             'green /* comment */',
             '/* comment */green',
             '/* comment */green/* comment */',
             '/* comment */  green  /* comment */',
             '/* comment *//**/  green  /* comment *//**/',
     ):
         sheet = css_parser.parseString('body {color: %s; }' % t)
         p = sheet.cssRules[0].style.getProperties()[0]
         self.assertEqual(p.valid, True)
     for t in (
             'gree',
             'gree /* comment */',
             '/* comment */gree',
             '/* comment */gree/* comment */',
             '/* comment */  gree  /* comment */',
             '/* comment *//**/  gree  /* comment *//**/',
     ):
         sheet = css_parser.parseString('body {color: %s; }' % t)
         p = sheet.cssRules[0].style.getProperties()[0]
         self.assertEqual(p.valid, False)
Example #4
0
    def test_cssRules(self):
        "CSSMediaRule.cssRules"
        r = css_parser.css.CSSMediaRule()
        self.assertEqual([], r.cssRules)
        sr = css_parser.css.CSSStyleRule()
        r.cssRules.append(sr)
        self.assertEqual([sr], r.cssRules)
        ir = css_parser.css.CSSImportRule()
        self.assertRaises(xml.dom.HierarchyRequestErr, r.cssRules.append, ir)

        s = css_parser.parseString('@media all { /*1*/a {x:1} }')
        m = s.cssRules[0]
        self.assertEqual(2, m.cssRules.length)
        del m.cssRules[0]
        self.assertEqual(1, m.cssRules.length)
        m.cssRules.append('/*2*/')
        self.assertEqual(2, m.cssRules.length)
        m.cssRules.extend(css_parser.parseString('/*3*/x {y:2}').cssRules)
        self.assertEqual(4, m.cssRules.length)
        self.assertEqual(
            '@media all {\n    a {\n        x: 1\n        }\n    /*2*/\n    /*3*/\n    x {\n        y: 2\n        }\n    }',
            m.cssText)

        for rule in m.cssRules:
            self.assertEqual(rule.parentStyleSheet, s)
            self.assertEqual(rule.parentRule, m)
Example #5
0
    def test_parsevalidation(self):
        style = 'color: 1'
        t = 'a { %s }' % style

        css_parser.log.setLevel(logging.DEBUG)

        # sheet
        s = self._setHandler()
        css_parser.parseString(t)
        self.assertNotEqual(len(s.getvalue()), 0)

        s = self._setHandler()
        css_parser.parseString(t, validate=False)
        self.assertEqual(s.getvalue(), '')

        # style
        s = self._setHandler()
        css_parser.parseStyle(style)
        self.assertNotEqual(len(s.getvalue()), 0)

        s = self._setHandler()
        css_parser.parseStyle(style, validate=True)
        self.assertNotEqual(len(s.getvalue()), 0)

        s = self._setHandler()
        css_parser.parseStyle(style, validate=False)
        self.assertEqual(s.getvalue(), '')
Example #6
0
    def test_set(self):
        "settings.set()"
        css_parser.ser.prefs.useMinified()
        text = 'a {filter: progid:DXImageTransform.Microsoft.BasicImage( rotation = 90 )}'

        self.assertEqual(css_parser.parseString(text).cssText, ''.encode())

        css_parser.settings.set('DXImageTransform.Microsoft', True)
        self.assertEqual(css_parser.parseString(text).cssText,
                         'a{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=90)}'.encode())

        css_parser.ser.prefs.useDefaults()
Example #7
0
    def test_attributes(self):
        "css_parser.parseString(href, media)"
        s = css_parser.parseString("a{}",
                                   href="file:foo.css",
                                   media="screen, projection, tv")
        self.assertEqual(s.href, "file:foo.css")
        self.assertEqual(s.media.mediaText, "screen, projection, tv")

        s = css_parser.parseString("a{}",
                                   href="file:foo.css",
                                   media=["screen", "projection", "tv"])
        self.assertEqual(s.media.mediaText, "screen, projection, tv")
Example #8
0
    def test_escapes(self):
        "css_parser escapes"
        css = r'\43\x { \43\x: \43\x !import\41nt }'
        sheet = css_parser.parseString(css)
        self.assertEqual(sheet.cssText, r'''C\x {
    c\x: C\x !important
    }'''.encode())

        css = r'\ x{\ x :\ x ;y:1} '
        sheet = css_parser.parseString(css)
        self.assertEqual(sheet.cssText, r'''\ x {
    \ x: \ x;
    y: 1
    }'''.encode())
Example #9
0
 def test_omitLastSemicolon(self):
     "Preferences.omitLastSemicolon"
     css = 'a { x: 1; y: 2 }'
     s = css_parser.parseString(css)
     self.assertEqual('a {\n    x: 1;\n    y: 2\n    }'.encode(), s.cssText)
     css_parser.ser.prefs.omitLastSemicolon = False
     self.assertEqual('a {\n    x: 1;\n    y: 2;\n    }'.encode(), s.cssText)
Example #10
0
 def test_defaultPropertyPriority(self):
     "Preferences.defaultPropertyPriority"
     css = 'a {\n    color: green !IM\\portant\n    }'
     s = css_parser.parseString(css)
     self.assertEqual(s.cssText, 'a {\n    color: green !important\n    }'.encode())
     css_parser.ser.prefs.defaultPropertyPriority = False
     self.assertEqual(s.cssText, css.encode())
    def test_styleSheet(self):
        "CSSImportRule.styleSheet"
        def fetcher(url):
            if url == "/root/level1/anything.css":
                return None, '@import "level2/css.css" "title2";'
            else:
                return None, 'a { color: red }'

        parser = css_parser.CSSParser(fetcher=fetcher)
        sheet = parser.parseString('''@charset "ascii";
                                   @import "level1/anything.css" tv "title";''',
                                   href='/root/')

        self.assertEqual(sheet.href, '/root/')

        ir = sheet.cssRules[1]
        self.assertEqual(ir.href, 'level1/anything.css')
        self.assertEqual(ir.styleSheet.href, '/root/level1/anything.css')
        # inherits ascii as no self charset is set
        self.assertEqual(ir.styleSheet.encoding, 'ascii')
        self.assertEqual(ir.styleSheet.ownerRule, ir)
        self.assertEqual(ir.styleSheet.media.mediaText, 'tv')
        self.assertEqual(ir.styleSheet.parentStyleSheet, None)  # sheet
        self.assertEqual(ir.styleSheet.title, 'title')
        self.assertEqual(ir.styleSheet.cssText,
                         '@charset "ascii";\n@import "level2/css.css" "title2";'.encode())

        ir2 = ir.styleSheet.cssRules[1]
        self.assertEqual(ir2.href, 'level2/css.css')
        self.assertEqual(ir2.styleSheet.href, '/root/level1/level2/css.css')
        # inherits ascii as no self charset is set
        self.assertEqual(ir2.styleSheet.encoding, 'ascii')
        self.assertEqual(ir2.styleSheet.ownerRule, ir2)
        self.assertEqual(ir2.styleSheet.media.mediaText, 'all')
        self.assertEqual(ir2.styleSheet.parentStyleSheet, None)  # ir.styleSheet
        self.assertEqual(ir2.styleSheet.title, 'title2')
        self.assertEqual(ir2.styleSheet.cssText,
                         '@charset "ascii";\na {\n    color: red\n    }'.encode())

        sheet = css_parser.parseString('@import "CANNOT-FIND.css";')
        ir = sheet.cssRules[0]
        self.assertEqual(ir.href, "CANNOT-FIND.css")
        self.assertEqual(type(ir.styleSheet), css_parser.css.CSSStyleSheet)

        def fetcher(url):
            if url.endswith('level1.css'):
                return None, '@charset "ascii"; @import "level2.css";'.encode()
            else:
                return None, 'a { color: red }'.encode()

        parser = css_parser.CSSParser(fetcher=fetcher)

        sheet = parser.parseString('@charset "iso-8859-1";@import "level1.css";')
        self.assertEqual(sheet.encoding, 'iso-8859-1')

        sheet = sheet.cssRules[1].styleSheet
        self.assertEqual(sheet.encoding, 'ascii')

        sheet = sheet.cssRules[1].styleSheet
        self.assertEqual(sheet.encoding, 'ascii')
Example #12
0
    def test_replaceUrls(self):
        "css_parser.replaceUrls()"
        css_parser.ser.prefs.keepAllProperties = True

        css = r'''
        @import "im1";
        @import url(im2);
        a {
            background-image: url(c) !important;
            background-\image: url(b);
            background: url(a) no-repeat !important;
            }'''
        s = css_parser.parseString(css)
        css_parser.replaceUrls(s, lambda old: "NEW" + old)
        self.assertEqual('@import "NEWim1";', s.cssRules[0].cssText)
        self.assertEqual('NEWim2', s.cssRules[1].href)
        self.assertEqual(
            '''background-image: url(NEWc) !important;
background-\\image: url(NEWb);
background: url(NEWa) no-repeat !important''', s.cssRules[2].style.cssText)

        css_parser.ser.prefs.keepAllProperties = False

        # CSSStyleDeclaration
        style = css_parser.parseStyle('''color: red;
                                        background-image:
                                            url(1.png),
                                            url('2.png')''')
        css_parser.replaceUrls(style, lambda url: 'prefix/' + url)
        self.assertEqual(
            style.cssText, '''color: red;
background-image: url(prefix/1.png), url(prefix/2.png)''')
Example #13
0
    def test_list(self):
        "PropertyValue[index]"
        # issue #41
        css = """div.one {color: rgb(255, 0, 0);}   """
        sheet = css_parser.parseString(css)
        pv = sheet.cssRules[0].style.getProperty('color').propertyValue
        self.assertEqual(pv.value, 'rgb(255, 0, 0)')
        self.assertEqual(pv[0].value, 'rgb(255, 0, 0)')

        # issue #42
        sheet = css_parser.parseString('body { font-family: "A", b, serif }')
        pv = sheet.cssRules[0].style.getProperty('font-family').propertyValue
        self.assertEqual(3, pv.length)
        self.assertEqual(pv[0].value, 'A')
        self.assertEqual(pv[1].value, 'b')
        self.assertEqual(pv[2].value, 'serif')
Example #14
0
    def test_getUrls(self):
        "css_parser.getUrls()"
        css_parser.ser.prefs.keepAllProperties = True

        css = r'''
        @import "im1";
        @import url(im2);
        @import url( im3 );
        @import url( "im4" );
        @import url( 'im5' );
        a {
            background-image: url(a) !important;
            background-\image: url(b);
            background: url(c) no-repeat !important;
            /* issue #46 */
            src: local("xx"),
                 url("f.woff") format("woff"),
                 url("f.otf") format("opentype"),
                 url("f.svg#f") format("svg");
            }'''
        urls = set(css_parser.getUrls(css_parser.parseString(css)))
        self.assertEqual(
            urls,
            set([
                "im1", "im2", "im3", "im4", "im5", "a", "b", "c", 'f.woff',
                'f.svg#f', 'f.otf'
            ]))
        css_parser.ser.prefs.keepAllProperties = False
Example #15
0
 def test_keepComments(self):
     "Preferences.keepComments"
     s = css_parser.parseString('/*1*/ a { /*2*/ }')
     css_parser.ser.prefs.keepComments = False
     self.assertEqual(''.encode(), s.cssText)
     css_parser.ser.prefs.keepEmptyRules = True
     self.assertEqual('a {}'.encode(), s.cssText)
Example #16
0
def html_css_stylesheet():
    global _html_css_stylesheet
    if _html_css_stylesheet is None:
        with open(P('templates/html.css'), 'rb') as f:
            html_css = f.read().decode('utf-8')
        _html_css_stylesheet = parseString(html_css, validate=False)
    return _html_css_stylesheet
Example #17
0
 def test_keepUsedNamespaceRulesOnly(self):
     "Preferences.keepUsedNamespaceRulesOnly"
     tests = {
         # default == prefix => both are combined
         '@namespace p "u"; @namespace "u"; p|a, a {top: 0}':
         ('@namespace "u";\na, a {\n    top: 0\n    }',
          '@namespace "u";\na, a {\n    top: 0\n    }'),
         '@namespace "u"; @namespace p "u"; p|a, a {top: 0}':
         ('@namespace p "u";\np|a, p|a {\n    top: 0\n    }',
          '@namespace p "u";\np|a, p|a {\n    top: 0\n    }'),
         # default and prefix
         '@namespace p "u"; @namespace "d"; p|a, a {top: 0}':
         ('@namespace p "u";\n@namespace "d";\np|a, a {\n    top: 0\n    }',
          '@namespace p "u";\n@namespace "d";\np|a, a {\n    top: 0\n    }'
          ),
         # prefix only
         '@namespace p "u"; @namespace "d"; p|a {top: 0}':
         ('@namespace p "u";\n@namespace "d";\np|a {\n    top: 0\n    }',
          '@namespace p "u";\np|a {\n    top: 0\n    }'),
         # default only
         '@namespace p "u"; @namespace "d"; a {top: 0}':
         ('@namespace p "u";\n@namespace "d";\na {\n    top: 0\n    }',
          '@namespace "d";\na {\n    top: 0\n    }'),
         # prefix-ns only
         '@namespace p "u"; @namespace d "d"; p|a {top: 0}':
         ('@namespace p "u";\n@namespace d "d";\np|a {\n    top: 0\n    }',
          '@namespace p "u";\np|a {\n    top: 0\n    }'),
     }
     for test in tests:
         s = css_parser.parseString(test)
         expwith, expwithout = tests[test]
         css_parser.ser.prefs.keepUsedNamespaceRulesOnly = False
         self.assertEqual(s.cssText, expwith.encode())
         css_parser.ser.prefs.keepUsedNamespaceRulesOnly = True
         self.assertEqual(s.cssText, expwithout.encode())
Example #18
0
 def test_minimizeColorHash(self):
     "Preferences.minimizeColorHash"
     css = 'a { color: #ffffff }'
     s = css_parser.parseString(css)
     self.assertEqual('a {\n    color: #fff\n    }'.encode(), s.cssText)
     css_parser.ser.prefs.minimizeColorHash = False
     self.assertEqual('a {\n    color: #ffffff\n    }'.encode(), s.cssText)
Example #19
0
 def test_propertyNameSpacer(self):
     "Preferences.propertyNameSpacer"
     css = 'a { x: 1; y: 2 }'
     s = css_parser.parseString(css)
     self.assertEqual('a {\n    x: 1;\n    y: 2\n    }'.encode(), s.cssText)
     css_parser.ser.prefs.propertyNameSpacer = ''
     self.assertEqual('a {\n    x:1;\n    y:2\n    }'.encode(), s.cssText)
Example #20
0
    def test_linesAfterRule(self):
        "Preferences.linesAfterRule"
        s = css_parser.parseString(
            'div {color:red;} @media screen {.aclass {width: 200px}}')
        expected_default = '''\
div {
    color: red
    }
@media screen {
    .aclass {
        width: 200px
        }
    }'''
        self.assertEqual(expected_default.encode(), s.cssText)
        css_parser.ser.prefs.linesAfterRules = 1 * '\n'
        expected_changed = '''\
div {
    color: red
    }

@media screen {
    .aclass {
        width: 200px
        }
    }
'''
        self.assertEqual(expected_changed.encode(), s.cssText)
Example #21
0
    def test_keepUnknownAtRules(self):
        "Preferences.keepUnknownAtRules"
        tests = {
            '''@three-dee {
              @background-lighting {
                azimuth: 30deg;
                elevation: 190deg;
              }
              h1 { color: red }
            }
            h1 { color: blue }''': ('''@three-dee {
    @background-lighting {
        azimuth: 30deg;
        elevation: 190deg;
        } h1 {
        color: red
        }
    }
h1 {
    color: blue
    }''', '''h1 {
    color: blue
    }''')
        }
        for test in tests:
            s = css_parser.parseString(test)
            expwith, expwithout = tests[test]
            css_parser.ser.prefs.keepUnknownAtRules = True
            self.assertEqual(s.cssText, expwith.encode())
            css_parser.ser.prefs.keepUnknownAtRules = False
            self.assertEqual(s.cssText, expwithout.encode())
Example #22
0
 def test_incomplete(self):
     "PropertyValue (incomplete)"
     tests = {'url("a': 'url(a)', 'url(a': 'url(a)'}
     for v, exp in tests.items():
         s = css_parser.parseString('a { background: %s' % v)
         v = s.cssRules[0].style.background
         self.assertEqual(v, exp)
Example #23
0
 def do_filter_css(self, css):
     from css_parser import parseString
     from css_parser.css import CSSRule
     sheet = parseString(css, validate=False)
     rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE))
     sel_map = {}
     count = 0
     for r in rules:
         # Check if we have only class selectors for this rule
         nc = [
             x for x in r.selectorList if not x.selectorText.startswith('.')
         ]
         if len(r.selectorList) > 1 and not nc:
             # Replace all the class selectors with a single class selector
             # This will be added to the class attribute of all elements
             # that have one of these selectors.
             replace_name = 'c_odt%d' % count
             count += 1
             for sel in r.selectorList:
                 s = sel.selectorText[1:]
                 if s not in sel_map:
                     sel_map[s] = []
                 sel_map[s].append(replace_name)
             r.selectorText = '.' + replace_name
     return sheet.cssText, sel_map
Example #24
0
def _get_css_links(
    content: bytes,
    base_url: str,
    headers: _Headers,
) -> Iterable[str]:
    """Get all links from a CSS file."""
    parsed = css_parser.parseString(content)
    return list(css_parser.getUrls(parsed))
Example #25
0
def html_css_stylesheet():
    global _html_css_stylesheet
    if _html_css_stylesheet is None:
        with open(pkg_resources.resource_filename('ebook_converter',
                                                  'data/html.css'), 'rb') as f:
            html_css = f.read().decode('utf-8')
        _html_css_stylesheet = parseString(html_css, validate=False)
    return _html_css_stylesheet
Example #26
0
 def test_defaultAtKeyword(self):
     "Preferences.defaultAtKeyword"
     s = css_parser.parseString('@im\\port "x";')
     self.assertEqual('@import "x";'.encode(), s.cssText)
     css_parser.ser.prefs.defaultAtKeyword = True
     self.assertEqual('@import "x";'.encode(), s.cssText)
     css_parser.ser.prefs.defaultAtKeyword = False
     self.assertEqual('@im\\port "x";'.encode(), s.cssText)
Example #27
0
def get_css_links(
    css_file: BinaryIO,
    base_url: str,
    headers: Optional[List[Tuple[str, str]]] = None,
) -> Iterable[str]:
    """Get all links from a CSS file."""
    text = css_file.read()
    parsed = css_parser.parseString(text)
    yield from css_parser.getUrls(parsed)
Example #28
0
    def test_CSSStyleSheet(self):
        "CSSSerializer.do_CSSStyleSheet"
        css = '/* κουρος */'
        sheet = css_parser.parseString(css)
        ans = sheet.cssText
        if isinstance(ans, bytes):
            ans = ans.decode('utf-8')
        self.assertEqual(css, ans)

        css = '@charset "utf-8";\n/* κουρος */'
        sheet = css_parser.parseString(css)
        ans = sheet.cssText
        if isinstance(ans, bytes):
            ans = ans.decode('utf-8')
        self.assertEqual(css, ans)
        sheet.cssRules[0].encoding = 'ascii'
        self.assertEqual('@charset "ascii";\n/* \\3BA \\3BF \\3C5 \\3C1 \\3BF \\3C2  */'.encode(),
                         sheet.cssText)
Example #29
0
 def test_lineSeparator(self):
     "Preferences.lineSeparator"
     s = css_parser.parseString('a { x:1;y:2}')
     self.assertEqual('a {\n    x: 1;\n    y: 2\n    }'.encode(), s.cssText)
     # cannot be indented as no split possible
     css_parser.ser.prefs.lineSeparator = ''
     self.assertEqual('a {x: 1;y: 2    }'.encode(), s.cssText)
     # no valid css but should work
     css_parser.ser.prefs.lineSeparator = 'XXX'
     self.assertEqual('a {XXX    x: 1;XXX    y: 2XXX    }'.encode(), s.cssText)
Example #30
0
    def test_parentRule(self):
        "CSSVariablesDeclaration.parentRule"
        s = css_parser.parseString('@variables { a:1}')
        r = s.cssRules[0]
        d = r.variables
        self.assertEqual(r, d.parentRule)

        d2 = css_parser.css.CSSVariablesDeclaration('b: 2')
        r.variables = d2
        self.assertEqual(r, d2.parentRule)
Example #31
0
 def __call__(self, oeb):
     if not self.body_font_family:
         return None
     if not self.href:
         iid, href = oeb.manifest.generate(u'page_styles', u'page_styles.css')
         rules = [css_text(x) for x in self.rules]
         rules = u'\n\n'.join(rules)
         sheet = css_parser.parseString(rules, validate=False)
         self.href = oeb.manifest.add(iid, href, guess_type(href)[0],
                 data=sheet).href
     return self.href
Example #32
0
 def replace_css(self, css):
     manifest = self.oeb.manifest
     for item in manifest.values():
         if item.media_type in OEB_STYLES:
             manifest.remove(item)
     id, href = manifest.generate('css', 'stylesheet.css')
     sheet = css_parser.parseString(css, validate=False)
     if self.transform_css_rules:
         from calibre.ebooks.css_transform_rules import transform_sheet
         transform_sheet(self.transform_css_rules, sheet)
     item = manifest.add(id, href, CSS_MIME, data=sheet)
     self.oeb.manifest.main_stylesheet = item
     return href
Example #33
0
    def get_embed_font_info(self, family, failure_critical=True):
        efi = []
        body_font_family = None
        if not family:
            return body_font_family, efi
        from calibre.utils.fonts.scanner import font_scanner, NoFonts
        from calibre.utils.fonts.utils import panose_to_css_generic_family
        try:
            faces = font_scanner.fonts_for_family(family)
        except NoFonts:
            msg = (u'No embeddable fonts found for family: %r'%family)
            if failure_critical:
                raise ValueError(msg)
            self.oeb.log.warn(msg)
            return body_font_family, efi
        if not faces:
            msg = (u'No embeddable fonts found for family: %r'%family)
            if failure_critical:
                raise ValueError(msg)
            self.oeb.log.warn(msg)
            return body_font_family, efi

        for i, font in enumerate(faces):
            ext = 'otf' if font['is_otf'] else 'ttf'
            fid, href = self.oeb.manifest.generate(id=u'font',
                href=u'fonts/%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext))
            item = self.oeb.manifest.add(fid, href,
                    guess_type('dummy.'+ext)[0],
                    data=font_scanner.get_font_data(font))
            item.unload_data_from_memory()

            cfont = {
                    u'font-family':u'"%s"'%font['font-family'],
                    u'panose-1': u' '.join(map(unicode_type, font['panose'])),
                    u'src': u'url(%s)'%item.href,
            }

            if i == 0:
                generic_family = panose_to_css_generic_family(font['panose'])
                body_font_family = u"'%s',%s"%(font['font-family'], generic_family)
                self.oeb.log(u'Embedding font: %s'%font['font-family'])
            for k in (u'font-weight', u'font-style', u'font-stretch'):
                if font[k] != u'normal':
                    cfont[k] = font[k]
            rule = '@font-face { %s }'%('; '.join(u'%s:%s'%(k, v) for k, v in
                iteritems(cfont)))
            rule = css_parser.parseString(rule)
            efi.append(rule)

        return body_font_family, efi
Example #34
0
 def dup_data(self):
     ''' Duplicate data so that any changes we make to markup/CSS only
     affect KF8 output and not MOBI 6 output '''
     self._data_cache = {}
     # Suppress css_parser logging output as it is duplicated anyway earlier
     # in the pipeline
     css_parser.log.setLevel(logging.CRITICAL)
     for item in self.oeb.manifest:
         if item.media_type in XML_DOCS:
             self._data_cache[item.href] = copy.deepcopy(item.data)
         elif item.media_type in OEB_STYLES:
             # I can't figure out how to make an efficient copy of the
             # in-memory CSSStylesheet, as deepcopy doesn't work (raises an
             # exception)
             self._data_cache[item.href] = css_parser.parseString(
                     item.data.cssText, validate=False)
Example #35
0
    def replace_resource_links(self):
        ''' Replace links to resources (raster images/fonts) with pointers to
        the MOBI record containing the resource. The pointers are of the form:
        kindle:embed:XXXX?mime=image/* The ?mime= is apparently optional and
        not used for fonts. '''

        def pointer(item, oref):
            ref = urlnormalize(item.abshref(oref))
            idx = self.resources.item_map.get(ref, None)
            if idx is not None:
                is_image = self.resources.records[idx-1][:4] not in {b'FONT'}
                idx = to_ref(idx)
                if is_image:
                    self.used_images.add(ref)
                    return 'kindle:embed:%s?mime=%s'%(idx,
                            self.resources.mime_map[ref])
                else:
                    return 'kindle:embed:%s'%idx
            return oref

        for item in self.oeb.manifest:

            if item.media_type in XML_DOCS:
                root = self.data(item)
                for tag in XPath('//h:img|//svg:image')(root):
                    for attr, ref in iteritems(tag.attrib):
                        if attr.split('}')[-1].lower() in {'src', 'href'}:
                            tag.attrib[attr] = pointer(item, ref)

                for tag in XPath('//h:style')(root):
                    if tag.text:
                        sheet = css_parser.parseString(tag.text, validate=False)
                        replacer = partial(pointer, item)
                        css_parser.replaceUrls(sheet, replacer,
                                ignoreImportRules=True)
                        repl = sheet.cssText
                        if isbytestring(repl):
                            repl = repl.decode('utf-8')
                        tag.text = '\n'+ repl + '\n'

            elif item.media_type in OEB_STYLES:
                sheet = self.data(item)
                replacer = partial(pointer, item)
                css_parser.replaceUrls(sheet, replacer, ignoreImportRules=True)
Example #36
0
    def collect_global_css(self):
        global_css = defaultdict(list)
        for item in self.items:
            stylizer = self.stylizers[item]
            if float(self.context.margin_top) >= 0:
                stylizer.page_rule['margin-top'] = '%gpt'%\
                        float(self.context.margin_top)
            if float(self.context.margin_bottom) >= 0:
                stylizer.page_rule['margin-bottom'] = '%gpt'%\
                        float(self.context.margin_bottom)
            items = sorted(stylizer.page_rule.items())
            css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
            css = ('@page {\n%s\n}\n'%css) if items else ''
            rules = [css_text(r) for r in stylizer.font_face_rules + self.embed_font_rules]
            raw = '\n\n'.join(rules)
            css += '\n\n' + raw
            global_css[css].append(item)

        gc_map = {}
        manifest = self.oeb.manifest
        for css in global_css:
            href = None
            if css.strip():
                id_, href = manifest.generate('page_css', 'page_styles.css')
                sheet = css_parser.parseString(css, validate=False)
                if self.transform_css_rules:
                    from calibre.ebooks.css_transform_rules import transform_sheet
                    transform_sheet(self.transform_css_rules, sheet)
                manifest.add(id_, href, CSS_MIME, data=sheet)
            gc_map[css] = href

        ans = {}
        for css, items in iteritems(global_css):
            for item in items:
                ans[item] = gc_map[css]
        return ans
Example #37
0
 def do_filter_css(self, css):
     from css_parser import parseString
     from css_parser.css import CSSRule
     sheet = parseString(css, validate=False)
     rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE))
     sel_map = {}
     count = 0
     for r in rules:
         # Check if we have only class selectors for this rule
         nc = [x for x in r.selectorList if not
                 x.selectorText.startswith('.')]
         if len(r.selectorList) > 1 and not nc:
             # Replace all the class selectors with a single class selector
             # This will be added to the class attribute of all elements
             # that have one of these selectors.
             replace_name = 'c_odt%d'%count
             count += 1
             for sel in r.selectorList:
                 s = sel.selectorText[1:]
                 if s not in sel_map:
                     sel_map[s] = []
                 sel_map[s].append(replace_name)
             r.selectorText = '.'+replace_name
     return sheet.cssText, sel_map
Example #38
0
    def __init__(self, tree, path, oeb, opts, profile=None,
            extra_css='', user_css='', base_css=''):
        self.oeb, self.opts = oeb, opts
        self.profile = profile
        if self.profile is None:
            # Use the default profile. This should really be using
            # opts.output_profile, but I don't want to risk changing it, as
            # doing so might well have hard to debug font size effects.
            from calibre.customize.ui import output_profiles
            for x in output_profiles():
                if x.short_name == 'default':
                    self.profile = x
                    break
        if self.profile is None:
            # Just in case the default profile is removed in the future :)
            self.profile = opts.output_profile
        self.body_font_size = self.profile.fbase
        self.logger = oeb.logger
        item = oeb.manifest.hrefs[path]
        basename = os.path.basename(path)
        cssname = os.path.splitext(basename)[0] + '.css'
        stylesheets = [html_css_stylesheet()]
        if base_css:
            stylesheets.append(parseString(base_css, validate=False))
        style_tags = xpath(tree, '//*[local-name()="style" or local-name()="link"]')

        # Add css_parser parsing profiles from output_profile
        for profile in self.opts.output_profile.extra_css_modules:
            cssprofiles.addProfile(profile['name'],
                                        profile['props'],
                                        profile['macros'])

        parser = CSSParser(fetcher=self._fetch_css_file,
                log=logging.getLogger('calibre.css'))
        self.font_face_rules = []
        for elem in style_tags:
            if (elem.tag == XHTML('style') and elem.get('type', CSS_MIME) in OEB_STYLES and media_ok(elem.get('media'))):
                text = elem.text if elem.text else u''
                for x in elem:
                    t = getattr(x, 'text', None)
                    if t:
                        text += u'\n\n' + force_unicode(t, u'utf-8')
                    t = getattr(x, 'tail', None)
                    if t:
                        text += u'\n\n' + force_unicode(t, u'utf-8')
                if text:
                    text = oeb.css_preprocessor(text)
                    # We handle @import rules separately
                    parser.setFetcher(lambda x: ('utf-8', b''))
                    stylesheet = parser.parseString(text, href=cssname,
                            validate=False)
                    parser.setFetcher(self._fetch_css_file)
                    for rule in stylesheet.cssRules:
                        if rule.type == rule.IMPORT_RULE:
                            ihref = item.abshref(rule.href)
                            if not media_ok(rule.media.mediaText):
                                continue
                            hrefs = self.oeb.manifest.hrefs
                            if ihref not in hrefs:
                                self.logger.warn('Ignoring missing stylesheet in @import rule:', rule.href)
                                continue
                            sitem = hrefs[ihref]
                            if sitem.media_type not in OEB_STYLES:
                                self.logger.warn('CSS @import of non-CSS file %r' % rule.href)
                                continue
                            stylesheets.append(sitem.data)
                    # Make links to resources absolute, since these rules will
                    # be folded into a stylesheet at the root
                    replaceUrls(stylesheet, item.abshref,
                            ignoreImportRules=True)
                    stylesheets.append(stylesheet)
            elif (elem.tag == XHTML('link') and elem.get('href') and elem.get(
                    'rel', 'stylesheet').lower() == 'stylesheet' and elem.get(
                    'type', CSS_MIME).lower() in OEB_STYLES and media_ok(elem.get('media'))
                ):
                href = urlnormalize(elem.attrib['href'])
                path = item.abshref(href)
                sitem = oeb.manifest.hrefs.get(path, None)
                if sitem is None:
                    self.logger.warn(
                        'Stylesheet %r referenced by file %r not in manifest' %
                        (path, item.href))
                    continue
                if not hasattr(sitem.data, 'cssRules'):
                    self.logger.warn(
                    'Stylesheet %r referenced by file %r is not CSS'%(path,
                        item.href))
                    continue
                stylesheets.append(sitem.data)
        csses = {'extra_css':extra_css, 'user_css':user_css}
        for w, x in csses.items():
            if x:
                try:
                    text = x
                    stylesheet = parser.parseString(text, href=cssname,
                            validate=False)
                    stylesheets.append(stylesheet)
                except:
                    self.logger.exception('Failed to parse %s, ignoring.'%w)
                    self.logger.debug('Bad css: ')
                    self.logger.debug(x)
        rules = []
        index = 0
        self.stylesheets = set()
        self.page_rule = {}
        for sheet_index, stylesheet in enumerate(stylesheets):
            href = stylesheet.href
            self.stylesheets.add(href)
            for rule in stylesheet.cssRules:
                if rule.type == rule.MEDIA_RULE:
                    if media_ok(rule.media.mediaText):
                        for subrule in rule.cssRules:
                            rules.extend(self.flatten_rule(subrule, href, index, is_user_agent_sheet=sheet_index==0))
                            index += 1
                else:
                    rules.extend(self.flatten_rule(rule, href, index, is_user_agent_sheet=sheet_index==0))
                    index = index + 1
        rules.sort(key=itemgetter(0))  # sort by specificity
        self.rules = rules
        self._styles = {}
        pseudo_pat = re.compile(u':{1,2}(%s)' % ('|'.join(INAPPROPRIATE_PSEUDO_CLASSES)), re.I)
        select = Select(tree, ignore_inappropriate_pseudo_classes=True)

        for _, _, cssdict, text, _ in rules:
            fl = pseudo_pat.search(text)
            try:
                matches = tuple(select(text))
            except SelectorError as err:
                self.logger.error('Ignoring CSS rule with invalid selector: %r (%s)' % (text, as_unicode(err)))
                continue

            if fl is not None:
                fl = fl.group(1)
                if fl == 'first-letter' and getattr(self.oeb,
                        'plumber_output_format', '').lower() in {u'mobi', u'docx'}:
                    # Fake first-letter
                    for elem in matches:
                        for x in elem.iter('*'):
                            if x.text:
                                punctuation_chars = []
                                text = unicode_type(x.text)
                                while text:
                                    category = unicodedata.category(text[0])
                                    if category[0] not in {'P', 'Z'}:
                                        break
                                    punctuation_chars.append(text[0])
                                    text = text[1:]

                                special_text = u''.join(punctuation_chars) + \
                                        (text[0] if text else u'')
                                span = x.makeelement('{%s}span' % XHTML_NS)
                                span.text = special_text
                                span.set('data-fake-first-letter', '1')
                                span.tail = text[1:]
                                x.text = None
                                x.insert(0, span)
                                self.style(span)._update_cssdict(cssdict)
                                break
                else:  # Element pseudo-class
                    for elem in matches:
                        self.style(elem)._update_pseudo_class(fl, cssdict)
            else:
                for elem in matches:
                    self.style(elem)._update_cssdict(cssdict)
        for elem in xpath(tree, '//h:*[@style]'):
            self.style(elem)._apply_style_attr(url_replacer=item.abshref)
        num_pat = re.compile(r'[0-9.]+$')
        for elem in xpath(tree, '//h:img[@width or @height]'):
            style = self.style(elem)
            # Check if either height or width is not default
            is_styled = style._style.get('width', 'auto') != 'auto' or \
                    style._style.get('height', 'auto') != 'auto'
            if not is_styled:
                # Update img style dimension using width and height
                upd = {}
                for prop in ('width', 'height'):
                    val = elem.get(prop, '').strip()
                    try:
                        del elem.attrib[prop]
                    except:
                        pass
                    if val:
                        if num_pat.match(val) is not None:
                            val += 'px'
                        upd[prop] = val
                if upd:
                    style._update_cssdict(upd)
Example #39
0
def html_css_stylesheet():
    global _html_css_stylesheet
    if _html_css_stylesheet is None:
        html_css = open(P('templates/html.css'), 'rb').read()
        _html_css_stylesheet = parseString(html_css, validate=False)
    return _html_css_stylesheet
Example #40
0
    def extract_css_into_flows(self):
        inlines = defaultdict(list)  # Ensure identical <style>s not repeated
        sheets = {}
        passthrough = getattr(self.opts, 'mobi_passthrough', False)

        for item in self.oeb.manifest:
            if item.media_type in OEB_STYLES:
                sheet = self.data(item)
                if not passthrough and not self.opts.expand_css and hasattr(item.data, 'cssText'):
                    condense_sheet(sheet)
                sheets[item.href] = len(self.flows)
                self.flows.append(sheet)

        def fix_import_rules(sheet):
            changed = False
            for rule in sheet.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
                if rule.href:
                    href = item.abshref(rule.href)
                    idx = sheets.get(href, None)
                    if idx is not None:
                        idx = to_ref(idx)
                        rule.href = 'kindle:flow:%s?mime=text/css'%idx
                        changed = True
            return changed

        for item in self.oeb.spine:
            root = self.data(item)

            for link in XPath('//h:link[@href]')(root):
                href = item.abshref(link.get('href'))
                idx = sheets.get(href, None)
                if idx is not None:
                    idx = to_ref(idx)
                    link.set('href', 'kindle:flow:%s?mime=text/css'%idx)

            for tag in XPath('//h:style')(root):
                p = tag.getparent()
                idx = p.index(tag)
                raw = tag.text
                if not raw or not raw.strip():
                    extract(tag)
                    continue
                sheet = css_parser.parseString(raw, validate=False)
                if fix_import_rules(sheet):
                    raw = force_unicode(sheet.cssText, 'utf-8')

                repl = etree.Element(XHTML('link'), type='text/css',
                        rel='stylesheet')
                repl.tail='\n'
                p.insert(idx, repl)
                extract(tag)
                inlines[raw].append(repl)

        for raw, elems in iteritems(inlines):
            idx = to_ref(len(self.flows))
            self.flows.append(raw)
            for link in elems:
                link.set('href', 'kindle:flow:%s?mime=text/css'%idx)

        for item in self.oeb.manifest:
            if item.media_type in OEB_STYLES:
                sheet = self.data(item)
                if hasattr(sheet, 'cssRules'):
                    fix_import_rules(sheet)

        for i, sheet in enumerate(tuple(self.flows)):
            if hasattr(sheet, 'cssText'):
                self.flows[i] = force_unicode(sheet.cssText, 'utf-8')