Changeset 2872:115acd0fe56a in livinglogic.python.xist

Show
Ignore:
Timestamp:
08/09/07 22:49:05 (12 years ago)
Author:
Walter Doerwald <walter@…>
Branch:
default
Message:

Move CSS related functionality to its own module and rename functions accordingly.

Rename itercssrules() to iterrules(), applycss() to applystylesheets()
and css() to selector().

Files:
2 added
1 removed
4 modified

Legend:

Unmodified
Added
Removed
  • NEWS.xml

    r2865 r2872  
    2020</item> 
    2121 
    22 <item>A generator function <function>itercssrules</function> has been added to 
    23 <module>ll.xist.ns.html</module>. This function can be passed an &xist; tree 
    24 and it will produce all &css; rules defined in any <class>link</class> or 
    25 <class>style</class> or imported by them (via the &css; rule <lit>@import</lit>) 
    26 This requires the <module>cssutils</module> package.</item> 
    27  
    28 <item>A function <function>applycss</function> has been added to 
    29 <module>ll.xist.ns.html</module>. This function modifies the &xist; tree 
    30 passed in by removing all &css; (from <class>link</class> and <class>style</class> 
     22<item><par>A new module <module>ll.xist.css</module> has been added which contains 
     23&css; related functionality: The generator function <function>iterrules</function> 
     24can be passed an &xist; tree and it will produce all &css; rules defined in any 
     25<class>html.link</class> or <class>html.style</class> elements or imported by them 
     26(via the &css; rule <lit>@import</lit>) This requires the <module>cssutils</module> 
     27package.</par> 
     28 
     29<par>The function <function>applystylesheets</function> modifies the &xist; tree 
     30passed in by removing all &css; (from <class>html.link</class> and <class>html.style</class> 
    3131elements and their <lit>@import</lit>ed stylesheets) and putting the styles into 
    32 <lit>style</lit> attributes of the affected elements instead.</item> 
     32<lit>style</lit> attributes of the affected elements instead.</par> 
     33 
     34<par>The function <function>selector</function> return a tree walk filter from 
     35a &css; selector passed as a string.</par> 
     36</item> 
     37 
     38<item>The <lit>media</lit> attribute of <class>html.link</class> and 
     39<class>html.style</class> now has a method <method>hasmedia</method>.</item> 
    3340 
    3441<item>The method <method>asBytes</method> has been renamed to <method>bytes</method> 
     
    6976<method>updatexisting</method> have been removed.</item> 
    7077 
    71 <item>The <lit>media</lit> attribute of <class>html.link</class> and 
    72 <class>html.style</class> now has a method <method>hasmedia</method>.</item> 
    7378</ulist> 
    7479</section> 
  • src/ll/xist/ns/html.py

    r2871 r2872  
    13841384    text = "\n".join(line.rstrip() for line in text.splitlines()) 
    13851385    return text 
    1386  
    1387  
    1388 try: 
    1389     import cssutils 
    1390     from cssutils import css, stylesheets 
    1391 except ImportError: 
    1392     pass 
    1393  
    1394  
    1395 def _isstyle(path): 
    1396     if path: 
    1397         node = path[-1] 
    1398         return (isinstance(node, style) and unicode(node.attrs["type"]) == "text/css") or (isinstance(node, link) and unicode(node.attrs["rel"]) == "stylesheet") 
    1399     return False 
    1400  
    1401  
    1402 def _fixurl(rule, base): 
    1403     if base is not None: 
    1404         for proplist in rule.style.seq: 
    1405             if not isinstance(proplist, css.CSSComment): 
    1406                 for prop in proplist: 
    1407                     newvalue = [] 
    1408                     for value in prop.cssValue.seq: 
    1409                         if value.startswith("url(") and value.endswith(")"): 
    1410                             value = "url(%s)" % (base/value[4:-1]) 
    1411                         newvalue.append(value) 
    1412                     prop.cssValue = "".join(newvalue) 
    1413  
    1414  
    1415 def _getmedia(stylesheet): 
    1416     while stylesheet is not None: 
    1417         if stylesheet.media is not None: 
    1418             # FIXME: remove extensions: see http://www.w3.org/TR/css3-mediaqueries/#idx-media-descriptor-1 
    1419             return stylesheet.media 
    1420         stylesheet = stylesheet.parentStyleSheet 
    1421     return None 
    1422  
    1423  
    1424 def _doimport(wantmedia, parentsheet, base): 
    1425     havemedia = _getmedia(parentsheet) 
    1426     if wantmedia is None or not havemedia or wantmedia in havemedia: 
    1427         for rule in parentsheet.cssRules: 
    1428             if rule.type == css.CSSRule.IMPORT_RULE: 
    1429                 href = url.URL(rule.href) 
    1430                 if base is not None: 
    1431                     href = base/href 
    1432                 havemedia = rule.media 
    1433                 with contextlib.closing(href.open("rb")) as r: 
    1434                     href = r.finalurl() 
    1435                     text = r.read() 
    1436                 sheet = css.CSSStyleSheet(href=str(href), media=havemedia, parentStyleSheet=parentsheet) 
    1437                 sheet.cssText = text 
    1438                 for rule in self._doimport(wantmedia, sheet, href): 
    1439                     yield rule 
    1440             elif rule.type == css.CSSRule.MEDIA_RULE: 
    1441                 if wantmedia in rule.media: 
    1442                     for subrule in rule.cssRules: 
    1443                         _fixurl(subrule, base) 
    1444                         yield subrule 
    1445             elif rule.type == css.CSSRule.STYLE_RULE: 
    1446                 _fixurl(rule, base) 
    1447                 yield rule 
    1448  
    1449  
    1450 def itercssrules(node, base=None, media=None): 
    1451     """ 
    1452     Return an iterator for all &css; rules defined in the &html; tree <arg>node</arg>. 
    1453     This will parse the &css; defined in any <class>style</class> or <class>link</class> 
    1454     element (and recursively in those stylesheets imported via the <lit>@import</lit> 
    1455     rule). The rules will be returned as <class>CSSStyleRule</class> objects 
    1456     from the <module>cssutils</module> package (so this requires <module>cssutils</module>). 
    1457     The <arg>base</arg> argument will be used as the base &url; for parsing the 
    1458     stylesheet references in the tree (so <lit>None</lit> means the &url;s will be 
    1459     used exactly as they appear in the tree). All &url;s in the style properties 
    1460     will be resolved. If <arg>media</arg> is given, only rules that apply to this 
    1461     media type will be produced. 
    1462     """ 
    1463     if base is not None: 
    1464         base = url.URL(base) 
    1465  
    1466     def doiter(node, base, media): 
    1467         for cssnode in node.walknode(_isstyle): 
    1468             if isinstance(cssnode, style): 
    1469                 href = str(self.base) if base is not None else None 
    1470                 if cssnode.attrs.media.hasmedia(media): 
    1471                     stylesheet = cssutils.parseString(unicode(cssnode.content), href=href, media=unicode(cssnode.attrs.media)) 
    1472                     for rule in _doimport(media, stylesheet, base): 
    1473                         yield rule 
    1474             else: # link 
    1475                 if "href" in cssnode.attrs: 
    1476                     href = cssnode.attrs["href"].asURL() 
    1477                     if base is not None: 
    1478                         href = self.base/href 
    1479                     if cssnode.attrs.media.hasmedia(media): 
    1480                         with contextlib.closing(href.open("rb")) as r: 
    1481                             s = r.read() 
    1482                         stylesheet = cssutils.parseString(unicode(s), href=str(href), media=unicode(cssnode.attrs.media)) 
    1483                         for rule in _doimport(media, stylesheet, href): 
    1484                             yield rule 
    1485     return misc.Iterator(doiter(node, base, media)) 
    1486  
    1487  
    1488 def applycss(node, base=None, media=None): 
    1489     def iterstyles(node, rules): 
    1490         if "style" in node.attrs: 
    1491             style = node.attrs["style"] 
    1492             if not style.isfancy(): 
    1493                 styledata = ( 
    1494                     xfind.CSSWeight(1, 0, 0), 
    1495                     xfind.IsSelector(node), 
    1496                     cssutils.parseString(u"*{%s}" % style).cssRules[0].style # parse the style out of the style attribute 
    1497                 ) 
    1498                 # put the style attribute into the order as the last of the selectors with ID weight (see http://www.w3.org/TR/REC-CSS1#cascading-order) 
    1499                 def doiter(): 
    1500                     done = False 
    1501                     for data in rules: 
    1502                         if not done and data[0] > styledata[0]: 
    1503                             yield styledata 
    1504                             done = True 
    1505                         yield data 
    1506                     if not done: 
    1507                         yield styledata 
    1508                 return doiter() 
    1509         return rules 
    1510      
    1511     rules = [] 
    1512     for (i, rule) in enumerate(itercssrules(node, base=base, media=media)): 
    1513         for selector in rule.selectorList: 
    1514             selector = xfind.css(selector) 
    1515             rules.append((selector.cssweight(), selector, rule.style)) 
    1516     rules.sort(key=operator.itemgetter(0)) 
    1517     count = 0 
    1518     for path in node.walk(xsc.Element): 
    1519         del path[-1][_isstyle] # drop style sheet nodes 
    1520         if path[-1].Attrs.isallowed("style"): 
    1521             styles = {} 
    1522             for (weight, selector, style) in iterstyles(path[-1], rules): 
    1523                 if selector.match(path): 
    1524                     for prop in style.seq: 
    1525                         if not isinstance(prop, css.CSSComment): 
    1526                             for value in prop: 
    1527                                 styles[prop.name] = (count, prop.name, value.value) 
    1528                                 count += 1 
    1529                     style = " ".join("%s: %s;" % (name, value) for (count, name, value) in sorted(styles.itervalues())) 
    1530                     if style: 
    1531                         path[-1].attrs["style"] = style 
    15321386 
    15331387 
  • src/ll/xist/xfind.py

    r2871 r2872  
    1919""" 
    2020 
    21  
    22 try: 
    23     import cssutils 
    24     from cssutils.css import cssstylerule 
    25     from cssutils.css import selector as cssselector 
    26     from cssutils.css import cssnamespacerule 
    27 except ImportError: 
    28     pass 
    2921 
    3022from ll import misc 
     
    12521244 
    12531245 
    1254 ### 
    1255 ### CSS helper functions 
    1256 ### 
    1257  
    1258 def _is_nth_node(iterator, node, index): 
    1259     # Return whether node is the index'th node in iterator (starting at 1) 
    1260     # index is an int or int string or "even" or "odd" 
    1261     if index == "even": 
    1262         for (i, child) in enumerate(iterator): 
    1263             if child is node: 
    1264                 return i % 2 == 1 
    1265         return False 
    1266     elif index == "odd": 
    1267         for (i, child) in enumerate(iterator): 
    1268             if child is node: 
    1269                 return i % 2 == 0 
    1270         return False 
    1271     else: 
    1272         if not isinstance(index, (int, long)): 
    1273             try: 
    1274                 index = int(index) 
    1275             except ValueError: 
    1276                 raise ValueError("illegal argument %r" % index) 
    1277             else: 
    1278                 if index < 1: 
    1279                     return False 
    1280         try: 
    1281             return iterator[index-1] is node 
    1282         except IndexError: 
    1283             return False 
    1284  
    1285  
    1286 def _is_nth_last_node(iterator, node, index): 
    1287     # Return whether node is the index'th last node in iterator 
    1288     # index is an int or int string or "even" or "odd" 
    1289     if index == "even": 
    1290         pos = None 
    1291         for (i, child) in enumerate(iterator): 
    1292             if child is node: 
    1293                 pos = i 
    1294         return pos is None or (i-pos) % 2 == 1 
    1295     elif index == "odd": 
    1296         pos = None 
    1297         for (i, child) in enumerate(iterator): 
    1298             if child is node: 
    1299                 pos = i 
    1300         return pos is None or (i-pos) % 2 == 0 
    1301     else: 
    1302         if not isinstance(index, (int, long)): 
    1303             try: 
    1304                 index = int(index) 
    1305             except ValueError: 
    1306                 raise ValueError("illegal argument %r" % index) 
    1307             else: 
    1308                 if index < 1: 
    1309                     return False 
    1310         try: 
    1311             return iterator[-index] is node 
    1312         except IndexError: 
    1313             return False 
    1314  
    1315  
    1316 def _children_of_type(node, type): 
    1317     for child in node: 
    1318         if isinstance(child, xsc.Element) and child.xmlname == type: 
    1319             yield child 
    1320  
    1321  
    1322 ### 
    1323 ### CSS selectors 
    1324 ### 
    1325  
    1326 class CSSWeightedSelector(Selector): 
    1327     """ 
    1328     Base class for all &css: pseudo-class selectors. 
    1329     """ 
    1330     def cssweight(self): 
    1331         return CSSWeight(0, 1, 0) 
    1332  
    1333  
    1334 class CSSHasAttributeSelector(CSSWeightedSelector): 
    1335     """ 
    1336     A <class>CSSHasAttributeSelector</class> selector selects all element nodes 
    1337     that have an attribute with the specified &xml; name. 
    1338     """ 
    1339     def __init__(self, attributename): 
    1340         self.attributename = attributename 
    1341  
    1342     def match(self, path): 
    1343         if path: 
    1344             node = path[-1] 
    1345             if isinstance(node, xsc.Element) and node.Attrs.isallowed_xml(self.attributename): 
    1346                 return node.attrs.has_xml(self.attributename) 
    1347         return False 
    1348  
    1349     def __str__(self): 
    1350         return "%s(%r)" % (self.__class__.__name__, self.attributename) 
    1351  
    1352  
    1353 class CSSAttributeListSelector(CSSWeightedSelector): 
    1354     def __init__(self, attributename, attributevalue): 
    1355         self.attributename = attributename 
    1356         self.attributevalue = attributevalue 
    1357  
    1358     def match(self, path): 
    1359         if path: 
    1360             node = path[-1] 
    1361             if isinstance(node, xsc.Element) and node.Attrs.isallowed_xml(self.attributename): 
    1362                 attr = node.attrs.get_xml(self.attributename) 
    1363                 return self.attributevalue in unicode(attr).split() 
    1364         return False 
    1365  
    1366     def __str__(self): 
    1367         return "%s(%r, %r)" % (self.__class__.__name__, self.attributename, self.attributevalue) 
    1368  
    1369  
    1370 class CSSAttributeLangSelector(CSSWeightedSelector): 
    1371     def __init__(self, attributename, attributevalue): 
    1372         self.attributename = attributename 
    1373         self.attributevalue = attributevalue 
    1374  
    1375     def match(self, path): 
    1376         if path: 
    1377             node = path[-1] 
    1378             if isinstance(node, xsc.Element) and node.Attrs.isallowed_xml(self.attributename): 
    1379                 attr = node.attrs.get_xml(self.attributename) 
    1380                 parts = unicode(attr).split("-", 1) 
    1381                 if parts: 
    1382                     return parts[0] == self.attributevalue 
    1383         return False 
    1384  
    1385     def __str__(self): 
    1386         return "%s(%r, %r)" % (self.__class__.__name__, self.attributename, self.attributevalue) 
    1387  
    1388  
    1389 class CSSFirstChildSelector(CSSWeightedSelector): 
    1390     def match(self, path): 
    1391         return len(path) >= 2 and _is_nth_node(path[-2][xsc.Element], path[-1], 1) 
    1392  
    1393     def __str__(self): 
    1394         return "CSSFirstChildSelector()" 
    1395  
    1396  
    1397 class CSSLastChildSelector(CSSWeightedSelector): 
    1398     def match(self, path): 
    1399         return len(path) >= 2 and _is_nth_last_node(path[-2][xsc.Element], path[-1], 1) 
    1400  
    1401     def __str__(self): 
    1402         return "CSSLastChildSelector()" 
    1403  
    1404  
    1405 class CSSFirstOfTypeSelector(CSSWeightedSelector): 
    1406     def match(self, path): 
    1407         if len(path) >= 2: 
    1408             node = path[-1] 
    1409             return isinstance(node, xsc.Element) and _is_nth_node(misc.Iterator(_children_of_type(path[-2], node.xmlname)), node, 1) 
    1410         return False 
    1411  
    1412     def __str__(self): 
    1413         return "CSSFirstOfTypeSelector()" 
    1414  
    1415  
    1416 class CSSLastOfTypeSelector(CSSWeightedSelector): 
    1417     def match(self, path): 
    1418         if len(path) >= 2: 
    1419             node = path[-1] 
    1420             return isinstance(node, xsc.Element) and _is_nth_last_node(misc.Iterator(_children_of_type(path[-2], node.xmlname)), node, 1) 
    1421         return False 
    1422  
    1423     def __str__(self): 
    1424         return "CSSLastOfTypeSelector()" 
    1425  
    1426  
    1427 class CSSOnlyChildSelector(CSSWeightedSelector): 
    1428     def match(self, path): 
    1429         if len(path) >= 2: 
    1430             node = path[-1] 
    1431             if isinstance(node, xsc.Element): 
    1432                 for child in path[-2][xsc.Element]: 
    1433                     if child is not node: 
    1434                         return False 
    1435                 return True 
    1436         return False 
    1437  
    1438     def __str__(self): 
    1439         return "CSSOnlyChildSelector()" 
    1440  
    1441  
    1442 class CSSOnlyOfTypeSelector(CSSWeightedSelector): 
    1443     def match(self, path): 
    1444         if len(path) >= 2: 
    1445             node = path[-1] 
    1446             if isinstance(node, xsc.Element): 
    1447                 for child in _children_of_type(path[-2], node.xmlname): 
    1448                     if child is not node: 
    1449                         return False 
    1450                 return True 
    1451         return False 
    1452  
    1453     def __str__(self): 
    1454         return "CSSOnlyOfTypeSelector()" 
    1455  
    1456  
    1457 class CSSEmptySelector(CSSWeightedSelector): 
    1458     def match(self, path): 
    1459         if path: 
    1460             node = path[-1] 
    1461             if isinstance(node, xsc.Element): 
    1462                 for child in path[-1].content: 
    1463                     if isinstance(child, xsc.Element) or (isinstance(child, xsc.Text) and child): 
    1464                         return False 
    1465                 return True 
    1466         return False 
    1467  
    1468     def __str__(self): 
    1469         return "CSSEmptySelector()" 
    1470  
    1471  
    1472 class CSSRootSelector(CSSWeightedSelector): 
    1473     def match(self, path): 
    1474         return len(path) == 1 and isinstance(path[-1], xsc.Element) 
    1475  
    1476     def __str__(self): 
    1477         return "CSSRootSelector()" 
    1478  
    1479  
    1480 class CSSLinkSelector(CSSWeightedSelector): 
    1481     def match(self, path): 
    1482         if path: 
    1483             node = path[-1] 
    1484             return isinstance(node, xsc.Element) and node.xmlns=="http://www.w3.org/1999/xhtml" and node.xmlname=="a" and "href" in node.attrs 
    1485         return False 
    1486  
    1487     def __str__(self): 
    1488         return "%s()" % self.__class__.__name__ 
    1489  
    1490  
    1491 class CSSInvalidPseudoSelector(CSSWeightedSelector): 
    1492     def match(self, path): 
    1493         return False 
    1494  
    1495     def __str__(self): 
    1496         return "%s()" % self.__class__.__name__ 
    1497  
    1498  
    1499 class CSSHoverSelector(CSSInvalidPseudoSelector): 
    1500     pass 
    1501  
    1502  
    1503 class CSSActiveSelector(CSSInvalidPseudoSelector): 
    1504     pass 
    1505  
    1506  
    1507 class CSSVisitedSelector(CSSInvalidPseudoSelector): 
    1508     pass 
    1509  
    1510  
    1511 class CSSFocusSelector(CSSInvalidPseudoSelector): 
    1512     pass 
    1513  
    1514  
    1515 class CSSAfterSelector(CSSInvalidPseudoSelector): 
    1516     pass 
    1517  
    1518  
    1519 class CSSBeforeSelector(CSSInvalidPseudoSelector): 
    1520     pass 
    1521  
    1522  
    1523 class CSSFunctionSelector(CSSWeightedSelector): 
    1524     def __init__(self, value=None): 
    1525         self.value = value 
    1526  
    1527     def __str__(self): 
    1528         return "%s(%r)" % (self.__class__.__name__, self.value) 
    1529  
    1530  
    1531 class CSSNthChildSelector(CSSFunctionSelector): 
    1532     def match(self, path): 
    1533         if len(path) >= 2: 
    1534             node = path[-1] 
    1535             if isinstance(node, xsc.Element): 
    1536                 return _is_nth_node(path[-2][xsc.Element], node, self.value) 
    1537         return False 
    1538  
    1539  
    1540 class CSSNthLastChildSelector(CSSFunctionSelector): 
    1541     def match(self, path): 
    1542         if len(path) >= 2: 
    1543             node = path[-1] 
    1544             if isinstance(node, xsc.Element): 
    1545                 return _is_nth_last_node(path[-2][xsc.Element], node, self.value) 
    1546         return False 
    1547  
    1548  
    1549 class CSSNthOfTypeSelector(CSSFunctionSelector): 
    1550     def match(self, path): 
    1551         if len(path) >= 2: 
    1552             node = path[-1] 
    1553             if isinstance(node, xsc.Element): 
    1554                 return _is_nth_node(misc.Iterator(_children_of_type(path[-2], node.xmlname)), node, self.value) 
    1555         return False 
    1556  
    1557  
    1558 class CSSNthLastOfTypeSelector(CSSFunctionSelector): 
    1559     def match(self, path): 
    1560         if len(path) >= 2: 
    1561             node = path[-1] 
    1562             if isinstance(node, xsc.Element): 
    1563                 return _is_nth_last_node(misc.Iterator(_children_of_type(path[-2], node.xmlname)), node, self.value) 
    1564         return False 
    1565  
    1566  
    1567 class CSSTypeSelector(Selector): 
    1568     def __init__(self, type="*", xmlns="*", *selectors): 
    1569         self.type = type 
    1570         self.xmlns = xsc.nsname(xmlns) 
    1571         self.selectors = [] # id, class, attribute etc. selectors for this node 
    1572  
    1573     def match(self, path): 
    1574         if path: 
    1575             node = path[-1] 
    1576             if self.type == "*" or node.xmlname == self.type: 
    1577                 if self.xmlns == "*" or node.xmlns == self.xmlns: 
    1578                     for selector in self.selectors: 
    1579                         if not selector.match(path): 
    1580                             return False 
    1581                     return True 
    1582         return False 
    1583  
    1584     def __str__(self): 
    1585         v = [self.__class__.__name__, "("] 
    1586         if self.type != "*" or self.xmlns != "*" or self.selectors: 
    1587             v.append(repr(self.type)) 
    1588         if self.xmlns != "*" or self.selectors: 
    1589             v.append(", ") 
    1590             v.append(repr(self.xmlns)) 
    1591         for selector in self.selectors: 
    1592             v.append(", ") 
    1593             v.append(str(selector)) 
    1594         v.append(")") 
    1595         return "".join(v) 
    1596  
    1597     def cssweight(self): 
    1598         result = CSSWeight(0, 0, int(self.type != "*")) 
    1599         for selector in self.selectors: 
    1600             result += selector.cssweight() 
    1601         return result 
    1602  
    1603  
    1604 class CSSAdjacentSiblingCombinator(BinaryCombinator): 
    1605     """ 
    1606     <par>A <class>CSSAdjacentSiblingCombinator</class> work similar to an 
    1607     <class>AdjacentSiblingCombinator</class> except that only preceding elements 
    1608     are considered.</par> 
    1609     """ 
    1610  
    1611     def match(self, path): 
    1612         if len(path) >= 2 and self.right.match(path): 
    1613             # Find sibling 
    1614             node = path[-1] 
    1615             sibling = None 
    1616             for child in path[-2][xsc.Element]: 
    1617                 if child is node: 
    1618                     break 
    1619                 sibling = child 
    1620             if sibling is not None: 
    1621                 return self.left.match(path[:-1]+[sibling]) 
    1622         return False 
    1623  
    1624     def __str__(self): 
    1625         return "%s(%s, %s)" % (self.__class__.__name__, self.left, self.right) 
    1626  
    1627  
    1628 class CSSGeneralSiblingCombinator(BinaryCombinator): 
    1629     """ 
    1630     <par>A <class>CSSGeneralSiblingCombinator</class> work similar to an 
    1631     <class>GeneralSiblingCombinator</class> except that only preceding elements 
    1632     are considered.</par> 
    1633     """ 
    1634  
    1635     def match(self, path): 
    1636         if len(path) >= 2 and self.right.match(path): 
    1637             node = path[-1] 
    1638             for child in path[-2][xsc.Element]: 
    1639                 if child is node: # no previous element siblings 
    1640                     return False 
    1641                 if self.left.match(path[:-1]+[child]): 
    1642                     return True 
    1643         return False 
    1644  
    1645     def __str__(self): 
    1646         return "%s(%s, %s)" % (self.__class__.__name__, self.left, self.right) 
    1647  
    1648  
    1649 _attributecombinator2class = { 
    1650     "=": attrhasvalue_xml, 
    1651     "~=": CSSAttributeListSelector, 
    1652     "|=": CSSAttributeLangSelector, 
    1653     "^=": attrstartswith_xml, 
    1654     "$=": attrendswith_xml, 
    1655     "*=": attrcontains_xml, 
    1656 } 
    1657  
    1658 _combinator2class = { 
    1659     " ": DescendantCombinator, 
    1660     ">": ChildCombinator, 
    1661     "+": CSSAdjacentSiblingCombinator, 
    1662     "~": CSSGeneralSiblingCombinator, 
    1663 } 
    1664  
    1665 _pseudoname2class = { 
    1666     "first-child": CSSFirstChildSelector, 
    1667     "last-child": CSSLastChildSelector, 
    1668     "first-of-type": CSSFirstOfTypeSelector, 
    1669     "last-of-type": CSSLastOfTypeSelector, 
    1670     "only-child": CSSOnlyChildSelector, 
    1671     "only-of-type": CSSOnlyOfTypeSelector, 
    1672     "empty": CSSEmptySelector, 
    1673     "root": CSSRootSelector, 
    1674     "hover": CSSHoverSelector, 
    1675     "focus": CSSFocusSelector, 
    1676     "link": CSSLinkSelector, 
    1677     "visited": CSSVisitedSelector, 
    1678     "active": CSSActiveSelector, 
    1679     "after": CSSAfterSelector, 
    1680     "before": CSSBeforeSelector, 
    1681 } 
    1682  
    1683 _function2class = { 
    1684     "nth-child": CSSNthChildSelector, 
    1685     "nth-last-child": CSSNthLastChildSelector, 
    1686     "nth-of-type": CSSNthOfTypeSelector, 
    1687     "nth-last-of-type": CSSNthLastOfTypeSelector, 
    1688 } 
    1689  
    1690  
    1691 def css(selectors, prefixes=None): 
    1692     """ 
    1693     Create a walk filter that will yield all nodes that match the specified 
    1694     &css; expression. <arg>selectors</arg> can be a string or a 
    1695     <class>cssutils.css.selector.Selector</class> object. <arg>prefixes</arg> 
    1696     may is a mapping mapping namespace prefixes to namespace names. 
    1697     """ 
    1698  
    1699     if isinstance(selectors, basestring): 
    1700         if prefixes is not None: 
    1701             prefixes = dict((key, xsc.nsname(value)) for (key, value) in prefixes.iteritems()) 
    1702             selectors = "%s\n%s{}" % ("\n".join("@namespace %s %r;" % (key if key is not None else "", value) for (key, value) in prefixes.iteritems()), selectors) 
    1703         else: 
    1704             selectors = "%s{}" % selectors 
    1705         for rule in cssutils.CSSParser().parseString(selectors).cssRules: 
    1706             if isinstance(rule, cssstylerule.CSSStyleRule): 
    1707                 selectors = rule.selectorList 
    1708                 break 
    1709         else: 
    1710             raise ValueError("can't happen") 
    1711     elif isinstance(selectors, cssstylerule.CSSStyleRule): 
    1712         selectors = selectors.selectorList 
    1713     elif isinstance(selectors, cssselector.Selector): 
    1714         selectors = [selectors] 
    1715     else: 
    1716         raise TypeError("can't handle %r" % type(selectors)) 
    1717     orcombinators = [] 
    1718     for selector in selectors: 
    1719         rule = root = CSSTypeSelector() 
    1720         prefix = None 
    1721         attributename = None 
    1722         attributevalue = None 
    1723         combinator = None 
    1724         inattr = False 
    1725         for x in selector.seq: 
    1726             t = x["type"] 
    1727             v = x["value"] 
    1728             if t == "prefix": 
    1729                 prefix = v 
    1730             elif t == "pipe": 
    1731                 if prefix != "*": 
    1732                     try: 
    1733                         xmlns = prefixes[prefix] 
    1734                     except KeyError: 
    1735                         raise xsc.IllegalPrefixError(prefix) 
    1736                     rule.xmlns = xmlns 
    1737                 prefix = None 
    1738             elif t == "type": 
    1739                 rule.type = v 
    1740             elif t == "id": 
    1741                 rule.selectors.append(hasid(v.lstrip("#"))) 
    1742             elif t == "classname": 
    1743                 rule.selectors.append(hasclass(v)) 
    1744             elif t == "pseudoname": 
    1745                 try: 
    1746                     rule.selectors.append(_pseudoname2class[v]()) 
    1747                 except KeyError: 
    1748                     raise ValueError("unknown pseudoname %s" % v) 
    1749             elif t == "function": 
    1750                 try: 
    1751                     rule.selectors.append(_function2class[v.rstrip("(")]()) 
    1752                 except KeyError: 
    1753                     raise ValueError("unknown function %s" % v) 
    1754                 rule.function = v 
    1755             elif t == "functionvalue": 
    1756                 rule.selectors[-1].value = v 
    1757             elif t == "attributename": 
    1758                 attributename = v 
    1759             elif t == "attributevalue": 
    1760                 if (v.startswith("'") and v.endswith("'")) or (v.startswith('"') and v.endswith('"')): 
    1761                     v = v[1:-1] 
    1762                 attributevalue = v 
    1763             elif t == "attribute selector": 
    1764                 combinator = None 
    1765                 inattr = True 
    1766             elif t == "attribute selector end": 
    1767                 if combinator is None: 
    1768                     rule.selectors.append(CSSHasAttributeSelector(attributename)) 
    1769                 else: 
    1770                     try: 
    1771                         rule.selectors.append(_attributecombinator2class[combinator](attributename, attributevalue)) 
    1772                     except KeyError: 
    1773                         raise ValueError("unknown combinator %s" % attributevalue) 
    1774                 inattr = False 
    1775             elif t == "combinator": 
    1776                 if inattr: 
    1777                     combinator = v 
    1778                 else: 
    1779                     try: 
    1780                         rule = CSSTypeSelector() 
    1781                         root = _combinator2class[v](root, rule) 
    1782                     except KeyError: 
    1783                         raise ValueError("unknown combinator %s" % v) 
    1784                     xmlns = "*" 
    1785         orcombinators.append(root) 
    1786     return orcombinators[0] if len(orcombinators) == 1 else OrCombinator(*orcombinators) 
  • test/test_xfind.py

    r2831 r2872  
    1616from ll import misc 
    1717from ll.xist import xsc, xfind, parsers 
    18 from ll.xist.ns import html, specials 
     18from ll.xist.ns import html 
    1919 
    2020 
     
    326326    assert str(misc.item(e[xsc.Text], 10, "x")) == "x" 
    327327    assert str(misc.item(e[xsc.Text], -11, "x")) == "x" 
    328  
    329  
    330 def test_css(): 
    331     with html.div(id=1) as e: 
    332         with html.ul(id=2): 
    333             +html.li("foo") 
    334             +html.li() 
    335  
    336     assert list(e.walknode(xfind.css("div"))) == [e] 
    337     assert list(e.walknode(xfind.css("li"))) == [e[0][0], e[0][1]] 
    338     assert list(e.walknode(xfind.css("div#1"))) == [e] 
    339     assert list(e.walknode(xfind.css("#2"))) == [e[0]] 
    340     assert list(e.walknode(xfind.css(":empty"))) == [e[0][1]] 
    341     assert list(e.walknode(xfind.css("li:empty"))) == [e[0][1]] 
    342     assert list(e.walknode(xfind.css("div :empty"))) == [e[0][1]] 
    343     assert list(e.walknode(xfind.css("div>*:empty"))) == [] 
    344     assert list(e.walknode(xfind.css("div>:empty"))) == [] 
    345     assert list(e.walknode(xfind.css("*|li"))) == [e[0][0], e[0][1]] 
    346     assert list(e.walknode(xfind.css("h|li", prefixes={"h": html}))) == [e[0][0], e[0][1]] 
    347     assert list(e.walknode(xfind.css("h|li", prefixes={"h": specials}))) == [] 
    348  
    349     with xsc.Frag() as e: 
    350         +html.div("foo") 
    351         +xsc.Text("filler") 
    352         +html.p("foo") 
    353         +xsc.Text("filler") 
    354         +html.ul(html.li("foo")) 
    355  
    356     assert list(e.walknode(xfind.css("div + p"))) == [e[2]] 
    357     assert list(e.walknode(xfind.css("div + ul"))) == [] 
    358     assert list(e.walknode(xfind.css("ul + p"))) == [] 
    359     assert list(e.walknode(xfind.css("div ~ p"))) == [e[2]] 
    360     assert list(e.walknode(xfind.css("div ~ ul"))) == [e[4]] 
    361     assert list(e.walknode(xfind.css("p ~ div"))) == [] 
    362     assert list(e.walknode(xfind.css("div:first-child + p"))) == [e[2]] 
    363     assert list(e.walknode(xfind.css("*:first-child + p"))) == [e[2]] 
    364  
    365     with xsc.Frag() as e: 
    366         +html.span(html.b("hurz"), "gurk", html.em("hinz"), html.em("kunz")) 
    367         +html.em("hurz") 
    368         +html.em("hinz") 
    369         +xsc.Text("nix") 
    370         +html.i("kunz") 
    371  
    372     assert list(e.walknode(xfind.css("*:only-of-type"))) == [e[0], e[0][0], e[4]] 
    373     assert list(e.walknode(xfind.css("*:nth-child(1)"))) == [e[0], e[0][0]] 
    374     assert list(e.walknode(xfind.css("*:nth-child(2)"))) == [e[0][2], e[1]] 
    375     assert list(e.walknode(xfind.css("*:nth-last-child(1)"))) == [e[0][3], e[4]] 
    376     assert list(e.walknode(xfind.css("*:nth-last-child(2)"))) == [e[0][2], e[2]] 
    377     assert list(e.walknode(xfind.css("*:nth-of-type(1)"))) == [e[0], e[0][0], e[0][2], e[1], e[4]] 
    378     assert list(e.walknode(xfind.css("*:nth-of-type(2)"))) == [e[0][3], e[2]] 
    379     assert list(e.walknode(xfind.css("*:nth-last-of-type(1)"))) == [e[0], e[0][0], e[0][3], e[2], e[4]] 
    380     assert list(e.walknode(xfind.css("*:nth-last-of-type(2)"))) == [e[0][2], e[1]] 
    381  
    382     e = xsc.Frag(html.span(html.b("hurz"), "gurk")) 
    383     assert list(e.walknode(xfind.css("*:only-child"))) == [e[0], e[0][0]] 
    384  
    385     with xsc.Frag() as e: 
    386         +html.em(class_="gurk", lang="en") 
    387         +html.em(class_="gurk hurz", lang="en-us") 
    388         +html.em(class_="hurz", lang="de") 
    389  
    390     assert list(e.walknode(xfind.css("em[class='gurk']"))) == [e[0]] 
    391     assert list(e.walknode(xfind.css("em[class~='gurk']"))) == [e[0], e[1]] 
    392     assert list(e.walknode(xfind.css("em[lang|='en']"))) == [e[0], e[1]] 
    393  
    394  
    395 def test_cssweight(): 
    396     # from http://www.w3.org/TR/css3-selectors/#specificity 
    397     assert xfind.css("*").cssweight() == (0, 0, 0) 
    398     assert xfind.css("LI").cssweight() == (0, 0, 1) 
    399     assert xfind.css("UL LI").cssweight() == (0, 0, 2) 
    400     assert xfind.css("UL OL+LI").cssweight() == (0, 0, 3) 
    401     assert xfind.css("UL OL LI.red").cssweight() == (0, 1, 3) 
    402     assert xfind.css("LI.red.level").cssweight() == (0, 2, 1) 
    403     assert xfind.css("#x34y").cssweight() == (1, 0, 0) 
    404     # The following is not supported 
    405     # assert xfind.css("#s12:not(FOO)").cssweight() == (1, 0, 1)