diff --git a/scrapy/trunk/scrapy/tests/test_utils_markup.py b/scrapy/trunk/scrapy/tests/test_utils_markup.py new file mode 100644 index 000000000..3f7fb9fde --- /dev/null +++ b/scrapy/trunk/scrapy/tests/test_utils_markup.py @@ -0,0 +1,40 @@ +import unittest + +from scrapy.utils.markup import remove_entities, replace_tags + +class UtilsMarkupTest(unittest.TestCase): + + def test_remove_entities(self): + # make sure it always return uncode + assert isinstance(remove_entities('no entities'), unicode) + assert isinstance(remove_entities('Price: £100!'), unicode) + + # regular conversions + self.assertEqual(remove_entities(u'As low as £100!'), + u'As low as \xa3100!') + self.assertEqual(remove_entities('As low as £100!'), + u'As low as \xa3100!') + + # keep some entities + self.assertEqual(remove_entities('Low < High & Medium £ six', keep=['lt', 'amp']), + u'Low < High & Medium \xa3 six') + + # illegal entities + self.assertEqual(remove_entities('a < b &illegal; c � six', remove_illegal=False), + u'a < b &illegal; c � six') + self.assertEqual(remove_entities('a < b &illegal; c � six', remove_illegal=True), + u'a < b c six') + + def test_remove_tags(self): + # make sure it always return uncode + assert isinstance(replace_tags('no entities'), unicode) + + self.assertEqual(replace_tags(u'This text contains some tag'), + u'This text contains some tag') + + self.assertEqual(replace_tags('This text is very important', ' '), + u'This text is very im port ant') + + # multiline tags + self.assertEqual(replace_tags('Click here'), + u'Click here') diff --git a/scrapy/trunk/scrapy/utils/markup.py b/scrapy/trunk/scrapy/utils/markup.py new file mode 100644 index 000000000..6ae05a8b7 --- /dev/null +++ b/scrapy/trunk/scrapy/utils/markup.py @@ -0,0 +1,59 @@ +""" +Functions for dealing with markup text +""" + +import re +import htmlentitydefs + +_ent_re = re.compile(r'&(#?)(.+?);') +_tag_re = re.compile(r'<[a-zA-Z\/!].*?>', re.DOTALL) + +def remove_entities(text, keep=(), remove_illegal=True): + """Remove entities from the given text. + + 'text' can be a unicode string or a regular string encoded as 'utf-8' + + If 'keep' is passed (with a list of entity names) those entities will + be kept (they won't be removed). + + It supports both numeric (&#nnnn;) and named (  >) entities. + + If remove_illegal is True, entities that can't be converted are removed. + If remove_illegal is False, entities that can't be converted are kept "as + is". For more information see the tests. + + Always returns a unicode string (with the entities removed). + """ + + def convert_entity(m): + if m.group(1)=='#': + try: + return unichr(int(m.group(2))) + except ValueError: + if remove_illegal: + return u'' + else: + return u'&#%s;' % m.group(2) + try: + if m.group(2) in keep: + return '&%s;' % m.group(2) + else: + return unichr(htmlentitydefs.name2codepoint[m.group(2)]) + except KeyError: + if remove_illegal: + return u'' + else: + return u'&%s;' % m.group(2) + + return _ent_re.sub(convert_entity, text.decode('utf-8')) + + +def replace_tags(text, token=''): + """Replace all markup tags found in the given text by the given token. By + default token is a null string so it just remove all tags. + + 'text' can be a unicode string or a regular string encoded as 'utf-8' + + Always returns a unicode string. + """ + return _tag_re.sub(token, text.decode('utf-8'))