mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-03 19:17:02 -05:00 
			
		
		
		
	More HTML transform actions
This commit is contained in:
		
							parent
							
								
									dd2a97d226
								
							
						
					
					
						commit
						1097c34667
					
				@ -8,6 +8,7 @@ from html5_parser import parse
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from calibre.ebooks.oeb.parse_utils import XHTML
 | 
					from calibre.ebooks.oeb.parse_utils import XHTML
 | 
				
			||||||
from calibre.ebooks.oeb.base import OEB_DOCS, XPath
 | 
					from calibre.ebooks.oeb.base import OEB_DOCS, XPath
 | 
				
			||||||
 | 
					from calibre.ebooks.metadata.tag_mapper import uniq
 | 
				
			||||||
from calibre.utils.serialize import json_dumps, json_loads
 | 
					from calibre.utils.serialize import json_dumps, json_loads
 | 
				
			||||||
from css_selectors.select import Select, get_parsed_selector
 | 
					from css_selectors.select import Select, get_parsed_selector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,13 +40,13 @@ ACTION_MAP = {a.name: a for a in (
 | 
				
			|||||||
    Action('unwrap', _('Remove tag only'), _('Remove the tag but keep its contents')),
 | 
					    Action('unwrap', _('Remove tag only'), _('Remove the tag but keep its contents')),
 | 
				
			||||||
    Action('add_classes', _('Add classes'), _('Add the specified classes, for e.g.:') + ' bold green', _('Space separated class names')),
 | 
					    Action('add_classes', _('Add classes'), _('Add the specified classes, for e.g.:') + ' bold green', _('Space separated class names')),
 | 
				
			||||||
    Action('remove_classes', _('Remove classes'), _('Remove the specified classes, for e.g:') + ' bold green', _('Space separated class names')),
 | 
					    Action('remove_classes', _('Remove classes'), _('Remove the specified classes, for e.g:') + ' bold green', _('Space separated class names')),
 | 
				
			||||||
    Action('wrap', _('Wrap the tag'), _(
 | 
					 | 
				
			||||||
        'Wrap the tag in the specified tag, for example: {0} will wrap the tag in a DIV tag with class {1}').format(
 | 
					 | 
				
			||||||
            '<div class="box">', 'box'), _('An HTML opening tag')),
 | 
					 | 
				
			||||||
    Action('remove_attrs', _('Remove attributes'), _(
 | 
					    Action('remove_attrs', _('Remove attributes'), _(
 | 
				
			||||||
        'Remove the specified attributes from the tag. Multiple attribute names should be separated by spaces'), _('Space separated attribute names')),
 | 
					        'Remove the specified attributes from the tag. Multiple attribute names should be separated by spaces'), _('Space separated attribute names')),
 | 
				
			||||||
    Action('add_attrs', _('Add attributes'), _('Add the specified attributes, for e.g.:') + ' class="red" name="test"', _('Space separated attribute names')),
 | 
					    Action('add_attrs', _('Add attributes'), _('Add the specified attributes, for e.g.:') + ' class="red" name="test"', _('Space separated attribute names')),
 | 
				
			||||||
    Action('empty', _('Empty the tag'), _('Remove all contents from the tag')),
 | 
					    Action('empty', _('Empty the tag'), _('Remove all contents from the tag')),
 | 
				
			||||||
 | 
					    Action('wrap', _('Wrap the tag'), _(
 | 
				
			||||||
 | 
					        'Wrap the tag in the specified tag, for example: {0} will wrap the tag in a DIV tag with class {1}').format(
 | 
				
			||||||
 | 
					            '<div class="box">', 'box'), _('An HTML opening tag')),
 | 
				
			||||||
    Action('insert', _('Insert HTML at start'), _(
 | 
					    Action('insert', _('Insert HTML at start'), _(
 | 
				
			||||||
        'The specified HTML snippet is inserted after the opening tag. Note that only valid HTML snippets can be used without unclosed tags'),
 | 
					        'The specified HTML snippet is inserted after the opening tag. Note that only valid HTML snippets can be used without unclosed tags'),
 | 
				
			||||||
           _('HTML snippet')),
 | 
					           _('HTML snippet')),
 | 
				
			||||||
@ -180,10 +181,47 @@ def unwrap_tag(tag):
 | 
				
			|||||||
    return True
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_classes(classes, tag):
 | 
				
			||||||
 | 
					    orig_cls = tag.get('class', '')
 | 
				
			||||||
 | 
					    orig = list(filter(None, str.split(orig_cls)))
 | 
				
			||||||
 | 
					    new_cls = ' '.join(uniq(orig + classes))
 | 
				
			||||||
 | 
					    if new_cls != orig_cls:
 | 
				
			||||||
 | 
					        tag.set('class', new_cls)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def remove_classes(classes, tag):
 | 
				
			||||||
 | 
					    orig_cls = tag.get('class', '')
 | 
				
			||||||
 | 
					    orig = list(filter(None, str.split(orig_cls)))
 | 
				
			||||||
 | 
					    for x in classes:
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                orig.remove(x)
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					    new_cls = ' '.join(orig)
 | 
				
			||||||
 | 
					    if new_cls != orig_cls:
 | 
				
			||||||
 | 
					        tag.set('class', new_cls)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def remove_attrs(attrs, tag):
 | 
				
			||||||
 | 
					    changed = False
 | 
				
			||||||
 | 
					    for a in attrs:
 | 
				
			||||||
 | 
					        if tag.attrib.pop(a, None) is not None:
 | 
				
			||||||
 | 
					            changed = True
 | 
				
			||||||
 | 
					    return changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
action_map = {
 | 
					action_map = {
 | 
				
			||||||
    'rename': lambda data: partial(rename_tag, qualify_tag_name(data)),
 | 
					    'rename': lambda data: partial(rename_tag, qualify_tag_name(data)),
 | 
				
			||||||
    'remove': lambda data: remove_tag,
 | 
					    'remove': lambda data: remove_tag,
 | 
				
			||||||
    'unwrap': lambda data: unwrap_tag,
 | 
					    'unwrap': lambda data: unwrap_tag,
 | 
				
			||||||
 | 
					    'add_classes': lambda data: partial(add_classes, str.split(data)),
 | 
				
			||||||
 | 
					    'remove_classes': lambda data: partial(remove_classes, str.split(data)),
 | 
				
			||||||
 | 
					    'remove_attrs': lambda data: partial(remove_attrs, str.split(data)),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -375,6 +413,30 @@ def test(return_tests=False):  # {{{
 | 
				
			|||||||
            self.assertTrue(t('unwrap')(div[1]))
 | 
					            self.assertTrue(t('unwrap')(div[1]))
 | 
				
			||||||
            ax(div, '<div><div/>text<span>unwrap</span>tail</div>')
 | 
					            ax(div, '<div><div/>text<span>unwrap</span>tail</div>')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            p = r()[0]
 | 
				
			||||||
 | 
					            self.assertTrue(t('add_classes', 'a b')(p))
 | 
				
			||||||
 | 
					            self.ae(p.get('class'), 'a b')
 | 
				
			||||||
 | 
					            p = r('<p class="c a d">')[0]
 | 
				
			||||||
 | 
					            self.assertTrue(t('add_classes', 'a b')(p))
 | 
				
			||||||
 | 
					            self.ae(p.get('class'), 'c a d b')
 | 
				
			||||||
 | 
					            p = r('<p class="c a d">')[0]
 | 
				
			||||||
 | 
					            self.assertFalse(t('add_classes', 'a')(p))
 | 
				
			||||||
 | 
					            self.ae(p.get('class'), 'c a d')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            p = r()[0]
 | 
				
			||||||
 | 
					            self.assertFalse(t('remove_classes', 'a b')(p))
 | 
				
			||||||
 | 
					            self.ae(p.get('class'), None)
 | 
				
			||||||
 | 
					            p = r('<p class="c a a d">')[0]
 | 
				
			||||||
 | 
					            self.assertTrue(t('remove_classes', 'a')(p))
 | 
				
			||||||
 | 
					            self.ae(p.get('class'), 'c d')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            p = r()[0]
 | 
				
			||||||
 | 
					            self.assertFalse(t('remove_attrs', 'a b')(p))
 | 
				
			||||||
 | 
					            self.assertFalse(p.attrib)
 | 
				
			||||||
 | 
					            p = r('<p class="c" x="y" id="p">')[0]
 | 
				
			||||||
 | 
					            self.assertTrue(t('remove_attrs', 'class id')(p))
 | 
				
			||||||
 | 
					            self.ae(list(p.attrib), ['x'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestTransforms)
 | 
					    tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestTransforms)
 | 
				
			||||||
    if return_tests:
 | 
					    if return_tests:
 | 
				
			||||||
        return tests
 | 
					        return tests
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user