1from __future__ import unicode_literals 2import os 3import sys 4 5try: 6 import urlparse 7except ImportError: 8 import urllib.parse as urlparse 9 10import csv 11import cssutils 12 13import requests 14from lxml import etree 15from inlinestyler.cssselect import CSSSelector, ExpressionError 16 17 18class Conversion(object): 19 def __init__(self): 20 self.CSSErrors = [] 21 self.CSSUnsupportErrors = dict() 22 self.supportPercentage = 100 23 self.convertedHTML = "" 24 25 def perform(self, document, sourceHTML, sourceURL, encoding='unicode'): 26 aggregate_css = "" 27 28 # Retrieve CSS rel links from html pasted and aggregate into one string 29 CSSRelSelector = CSSSelector("link[rel=stylesheet],link[rel=StyleSheet],link[rel=STYLESHEET]") 30 matching = CSSRelSelector.evaluate(document) 31 for element in matching: 32 try: 33 csspath = element.get("href") 34 if len(sourceURL): 35 if element.get("href").lower().find("http://", 0) < 0: 36 parsed_url = urlparse.urlparse(sourceURL) 37 csspath = urlparse.urljoin(parsed_url.scheme + "://" + parsed_url.hostname, csspath) 38 39 css_content = requests.get(csspath).text 40 aggregate_css += ''.join(css_content) 41 42 element.getparent().remove(element) 43 except: 44 raise IOError('The stylesheet ' + element.get("href") + ' could not be found') 45 46 # Include inline style elements 47 CSSStyleSelector = CSSSelector("style,Style") 48 matching = CSSStyleSelector.evaluate(document) 49 for element in matching: 50 aggregate_css += element.text 51 element.getparent().remove(element) 52 53 # Convert document to a style dictionary compatible with etree 54 styledict = self.get_view(document, aggregate_css) 55 56 # Set inline style attribute if not one of the elements not worth styling 57 ignore_list = ['html', 'head', 'title', 'meta', 'link', 'script'] 58 for element, style in styledict.items(): 59 if element.tag not in ignore_list: 60 v = style.getCssText(separator='') 61 element.set('style', v) 62 63 self.convertedHTML = etree.tostring(document, method="html", pretty_print=True, encoding=encoding) 64 return self 65 66 def styleattribute(self, element): 67 """ 68 returns css.CSSStyleDeclaration of inline styles, for html: @style 69 """ 70 css_text = element.get('style') 71 if css_text: 72 return cssutils.css.CSSStyleDeclaration(cssText=css_text) 73 else: 74 return None 75 76 def get_view(self, document, css): 77 78 view = {} 79 specificities = {} 80 supportratios = {} 81 support_failrate = 0 82 support_totalrate = 0 83 compliance = dict() 84 85 with open(os.path.join(os.path.dirname(__file__), "css_compliance.csv")) as csv_file: 86 compat_list = csv_file.readlines() 87 88 mycsv = csv.DictReader(compat_list, delimiter=str(',')) 89 90 for row in mycsv: 91 # Count clients so we can calculate an overall support percentage later 92 client_count = len(row) 93 compliance[row['property'].strip()] = dict(row) 94 95 # Decrement client count to account for first col which is property name 96 client_count -= 1 97 98 sheet = cssutils.parseString(css) 99 100 rules = (rule for rule in sheet if rule.type == rule.STYLE_RULE) 101 for rule in rules: 102 103 for selector in rule.selectorList: 104 try: 105 cssselector = CSSSelector(selector.selectorText) 106 matching = cssselector.evaluate(document) 107 108 for element in matching: 109 # add styles for all matching DOM elements 110 if element not in view: 111 # add initial 112 view[element] = cssutils.css.CSSStyleDeclaration() 113 specificities[element] = {} 114 115 # add inline style if present 116 inlinestyletext = element.get('style') 117 if inlinestyletext: 118 inlinestyle = cssutils.css.CSSStyleDeclaration(cssText=inlinestyletext) 119 else: 120 inlinestyle = None 121 if inlinestyle: 122 for p in inlinestyle: 123 # set inline style specificity 124 view[element].setProperty(p) 125 specificities[element][p.name] = (1, 0, 0, 0) 126 127 for p in rule.style: 128 if p.name not in supportratios: 129 supportratios[p.name] = {'usage': 0, 'failedClients': 0} 130 131 supportratios[p.name]['usage'] += 1 132 133 try: 134 if p.name not in self.CSSUnsupportErrors: 135 for client, support in compliance[p.name].items(): 136 if support == "N" or support == "P": 137 # Increment client failure count for this property 138 supportratios[p.name]['failedClients'] += 1 139 if p.name not in self.CSSUnsupportErrors: 140 if support == "P": 141 self.CSSUnsupportErrors[p.name] = [client + ' (partial support)'] 142 else: 143 self.CSSUnsupportErrors[p.name] = [client] 144 else: 145 if support == "P": 146 self.CSSUnsupportErrors[p.name].append(client + ' (partial support)') 147 else: 148 self.CSSUnsupportErrors[p.name].append(client) 149 150 except KeyError: 151 pass 152 153 # update styles 154 if p not in view[element]: 155 view[element].setProperty(p.name, p.value, p.priority) 156 specificities[element][p.name] = selector.specificity 157 else: 158 sameprio = (p.priority == view[element].getPropertyPriority(p.name)) 159 if not sameprio and bool(p.priority) or (sameprio and selector.specificity >= specificities[element][p.name]): 160 # later, more specific or higher prio 161 view[element].setProperty(p.name, p.value, p.priority) 162 163 except ExpressionError: 164 if str(sys.exc_info()[1]) not in self.CSSErrors: 165 self.CSSErrors.append(str(sys.exc_info()[1])) 166 pass 167 168 for props, propvals in supportratios.items(): 169 support_failrate += (propvals['usage']) * int(propvals['failedClients']) 170 support_totalrate += int(propvals['usage']) * client_count 171 172 if support_failrate and support_totalrate: 173 self.supportPercentage = 100 - ((float(support_failrate) / float(support_totalrate)) * 100) 174 return view 175