Compare commits
	
		
			23 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5bcf7403ad | ||
|  | 2d6c092b65 | ||
|  | 6d0689cad6 | ||
|  | 3f80e93ee0 | ||
|  | 1b18abab1d | ||
|  | 03dd5af5ab | ||
|  | dfba82b07c | ||
|  | 08ca02c87f | ||
|  | b61f4ec095 | ||
|  | 9dbe6a494b | ||
|  | 44e70939d6 | ||
|  | ab6066eafa | ||
|  | 42258cdd36 | ||
|  | d3de9e6893 | ||
|  | 333beb94af | ||
|  | f3c0942c49 | ||
|  | 02adf53ab9 | ||
|  | 3497b5cab4 | ||
|  | 9c17dca17c | ||
|  | de342d3177 | ||
|  | 743b452daf | ||
|  | c762f3c337 | ||
|  | 31803d41bc | 
| @@ -95,7 +95,7 @@ classifiers. The core of machine learning algorithm lays in | |||||||
| apply to a message (``featurespace.py``), how data sets are built | apply to a message (``featurespace.py``), how data sets are built | ||||||
| (``dataset.py``), classifier’s interface (``classifier.py``). | (``dataset.py``), classifier’s interface (``classifier.py``). | ||||||
|  |  | ||||||
| The data used for training is taken from our personal email | Currently the data used for training is taken from our personal email | ||||||
| conversations and from `ENRON`_ dataset. As a result of applying our set | conversations and from `ENRON`_ dataset. As a result of applying our set | ||||||
| of features to the dataset we provide files ``classifier`` and | of features to the dataset we provide files ``classifier`` and | ||||||
| ``train.data`` that don’t have any personal information but could be | ``train.data`` that don’t have any personal information but could be | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ from setuptools import setup, find_packages | |||||||
|  |  | ||||||
|  |  | ||||||
| setup(name='talon', | setup(name='talon', | ||||||
|       version='1.2.2', |       version='1.2.9', | ||||||
|       description=("Mailgun library " |       description=("Mailgun library " | ||||||
|                    "to extract message quotations and signatures."), |                    "to extract message quotations and signatures."), | ||||||
|       long_description=open("README.rst").read(), |       long_description=open("README.rst").read(), | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ CHECKPOINT_PATTERN = re.compile(CHECKPOINT_PREFIX + '\d+' + CHECKPOINT_SUFFIX) | |||||||
|  |  | ||||||
| # HTML quote indicators (tag ids) | # HTML quote indicators (tag ids) | ||||||
| QUOTE_IDS = ['OLK_SRC_BODY_SECTION'] | QUOTE_IDS = ['OLK_SRC_BODY_SECTION'] | ||||||
|  | RE_FWD = re.compile("^[-]+[ ]*Forwarded message[ ]*[-]+$", re.I | re.M) | ||||||
|  |  | ||||||
|  |  | ||||||
| def add_checkpoint(html_note, counter): | def add_checkpoint(html_note, counter): | ||||||
| @@ -77,7 +78,7 @@ def delete_quotation_tags(html_note, counter, quotation_checkpoints): | |||||||
| def cut_gmail_quote(html_message): | def cut_gmail_quote(html_message): | ||||||
|     ''' Cuts the outermost block element with class gmail_quote. ''' |     ''' Cuts the outermost block element with class gmail_quote. ''' | ||||||
|     gmail_quote = html_message.cssselect('div.gmail_quote') |     gmail_quote = html_message.cssselect('div.gmail_quote') | ||||||
|     if gmail_quote: |     if gmail_quote and (gmail_quote[0].text is None or not RE_FWD.match(gmail_quote[0].text)): | ||||||
|         gmail_quote[0].getparent().remove(gmail_quote[0]) |         gmail_quote[0].getparent().remove(gmail_quote[0]) | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
| @@ -85,9 +86,12 @@ def cut_gmail_quote(html_message): | |||||||
| def cut_microsoft_quote(html_message): | def cut_microsoft_quote(html_message): | ||||||
|     ''' Cuts splitter block and all following blocks. ''' |     ''' Cuts splitter block and all following blocks. ''' | ||||||
|     splitter = html_message.xpath( |     splitter = html_message.xpath( | ||||||
|         #outlook 2007, 2010 |         #outlook 2007, 2010 (international) | ||||||
|         "//div[@style='border:none;border-top:solid #B5C4DF 1.0pt;" |         "//div[@style='border:none;border-top:solid #B5C4DF 1.0pt;" | ||||||
|         "padding:3.0pt 0cm 0cm 0cm']|" |         "padding:3.0pt 0cm 0cm 0cm']|" | ||||||
|  |         #outlook 2007, 2010 (american) | ||||||
|  |         "//div[@style='border:none;border-top:solid #B5C4DF 1.0pt;" | ||||||
|  |         "padding:3.0pt 0in 0in 0in']|" | ||||||
|         #windows mail |         #windows mail | ||||||
|         "//div[@style='padding-top: 5px; " |         "//div[@style='padding-top: 5px; " | ||||||
|         "border-top-color: rgb(229, 229, 229); " |         "border-top-color: rgb(229, 229, 229); " | ||||||
| @@ -172,8 +176,23 @@ def cut_from_block(html_message): | |||||||
|             parent_div_is_all_content = ( |             parent_div_is_all_content = ( | ||||||
|                 maybe_body is not None and maybe_body.tag == 'body' and |                 maybe_body is not None and maybe_body.tag == 'body' and | ||||||
|                 len(maybe_body.getchildren()) == 1) |                 len(maybe_body.getchildren()) == 1) | ||||||
|  |  | ||||||
|             if not parent_div_is_all_content: |             if not parent_div_is_all_content: | ||||||
|                 block.getparent().remove(block) |                 parent = block.getparent() | ||||||
|  |                 next_sibling = block.getnext() | ||||||
|  |  | ||||||
|  |                 # remove all tags after found From block | ||||||
|  |                 # (From block and quoted message are in separate divs) | ||||||
|  |                 while next_sibling is not None: | ||||||
|  |                     parent.remove(block) | ||||||
|  |                     block = next_sibling | ||||||
|  |                     next_sibling = block.getnext() | ||||||
|  |  | ||||||
|  |                 # remove the last sibling (or the | ||||||
|  |                 # From block if no siblings) | ||||||
|  |                 if block is not None: | ||||||
|  |                     parent.remove(block) | ||||||
|  |  | ||||||
|                 return True |                 return True | ||||||
|         else: |         else: | ||||||
|             return False |             return False | ||||||
| @@ -185,7 +204,17 @@ def cut_from_block(html_message): | |||||||
|          "//*[starts-with(mg:tail(), 'Date:')]")) |          "//*[starts-with(mg:tail(), 'Date:')]")) | ||||||
|     if block: |     if block: | ||||||
|         block = block[0] |         block = block[0] | ||||||
|  |  | ||||||
|  |         if RE_FWD.match(block.getparent().text or ''): | ||||||
|  |             return False | ||||||
|  |          | ||||||
|         while(block.getnext() is not None): |         while(block.getnext() is not None): | ||||||
|             block.getparent().remove(block.getnext()) |             block.getparent().remove(block.getnext()) | ||||||
|         block.getparent().remove(block) |         block.getparent().remove(block) | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|  | def cut_zimbra_quote(html_message): | ||||||
|  |     zDivider = html_message.xpath('//hr[@data-marker="__DIVIDER__"]') | ||||||
|  |     if zDivider: | ||||||
|  |         zDivider[0].getparent().remove(zDivider[0]) | ||||||
|  |         return True | ||||||
|   | |||||||
| @@ -148,7 +148,9 @@ SPLITTER_PATTERNS = [ | |||||||
|     re.compile("\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+GMT.*@", re.S), |     re.compile("\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+GMT.*@", re.S), | ||||||
|     # Thu, 26 Jun 2014 14:00:51 +0400 Bob <bob@example.com>: |     # Thu, 26 Jun 2014 14:00:51 +0400 Bob <bob@example.com>: | ||||||
|     re.compile('\S{3,10}, \d\d? \S{3,10} 20\d\d,? \d\d?:\d\d(:\d\d)?' |     re.compile('\S{3,10}, \d\d? \S{3,10} 20\d\d,? \d\d?:\d\d(:\d\d)?' | ||||||
|                '( \S+){3,6}@\S+:') |                '( \S+){3,6}@\S+:'), | ||||||
|  |     # Sent from Samsung MobileName <address@example.com> wrote: | ||||||
|  |     re.compile('Sent from Samsung .*@.*> wrote') | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -350,6 +352,7 @@ def extract_from_html(msg_body): | |||||||
|         parser=html.HTMLParser(encoding="utf-8") |         parser=html.HTMLParser(encoding="utf-8") | ||||||
|     ) |     ) | ||||||
|     cut_quotations = (html_quotations.cut_gmail_quote(html_tree) or |     cut_quotations = (html_quotations.cut_gmail_quote(html_tree) or | ||||||
|  |                       html_quotations.cut_zimbra_quote(html_tree) or | ||||||
|                       html_quotations.cut_blockquote(html_tree) or |                       html_quotations.cut_blockquote(html_tree) or | ||||||
|                       html_quotations.cut_microsoft_quote(html_tree) or |                       html_quotations.cut_microsoft_quote(html_tree) or | ||||||
|                       html_quotations.cut_by_id(html_tree) or |                       html_quotations.cut_by_id(html_tree) or | ||||||
|   | |||||||
| @@ -131,6 +131,17 @@ def test_gmail_quote(): | |||||||
|         RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body))) |         RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_gmail_quote_compact(): | ||||||
|  |     msg_body = 'Reply' \ | ||||||
|  |                '<div class="gmail_quote">' \ | ||||||
|  |                '<div class="gmail_quote">On 11-Apr-2011, at 6:54 PM, Bob <bob@example.com> wrote:' \ | ||||||
|  |                '<div>Test</div>' \ | ||||||
|  |                '</div>' \ | ||||||
|  |                '</div>' | ||||||
|  |     eq_("<html><body><p>Reply</p></body></html>", | ||||||
|  |         RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body))) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_gmail_quote_blockquote(): | def test_gmail_quote_blockquote(): | ||||||
|     msg_body = """Message |     msg_body = """Message | ||||||
| <blockquote class="gmail_quote"> | <blockquote class="gmail_quote"> | ||||||
| @@ -268,6 +279,26 @@ def test_reply_separated_by_hr(): | |||||||
|             '', quotations.extract_from_html(REPLY_SEPARATED_BY_HR))) |             '', quotations.extract_from_html(REPLY_SEPARATED_BY_HR))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_from_block_and_quotations_in_separate_divs(): | ||||||
|  |     msg_body = ''' | ||||||
|  | Reply | ||||||
|  | <div> | ||||||
|  |   <hr/> | ||||||
|  |   <div> | ||||||
|  |     <font> | ||||||
|  |       <b>From: bob@example.com</b> | ||||||
|  |       <b>Date: Thu, 24 Mar 2016 08:07:12 -0700</b> | ||||||
|  |     </font> | ||||||
|  |   </div> | ||||||
|  |   <div> | ||||||
|  |     Quoted message | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | ''' | ||||||
|  |     eq_('<html><body><p>Reply</p><div><hr></div></body></html>', | ||||||
|  |         RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body))) | ||||||
|  |  | ||||||
|  |  | ||||||
| def extract_reply_and_check(filename): | def extract_reply_and_check(filename): | ||||||
|     f = open(filename) |     f = open(filename) | ||||||
|  |  | ||||||
| @@ -340,3 +371,10 @@ def test_CRLF(): | |||||||
|     assert_false(symbol in extracted)     |     assert_false(symbol in extracted)     | ||||||
|     eq_("<html><body><p>Reply</p></body></html>", |     eq_("<html><body><p>Reply</p></body></html>", | ||||||
|         RE_WHITESPACE.sub('', extracted)) |         RE_WHITESPACE.sub('', extracted)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_gmail_forwarded_msg(): | ||||||
|  |     msg_body = """<div dir="ltr"><br><div class="gmail_quote">---------- Forwarded message ----------<br>From: <b class="gmail_sendername">Bob</b> <span dir="ltr"><<a href="mailto:bob@example.com">bob@example.com</a>></span><br>Date: Fri, Feb 11, 2010 at 5:59 PM<br>Subject: Bob WFH today<br>To: Mary <<a href="mailto:mary@example.com">mary@example.com</a>><br><br><br><div dir="ltr">eom</div> | ||||||
|  | </div><br></div>""" | ||||||
|  |     extracted = quotations.extract_from_html(msg_body) | ||||||
|  |     eq_(RE_WHITESPACE.sub('', msg_body), RE_WHITESPACE.sub('', extracted)) | ||||||
|   | |||||||
| @@ -32,6 +32,19 @@ On 11-Apr-2011, at 6:54 PM, Roman Tkachenko <romant@example.com> wrote: | |||||||
|     eq_("Test reply", quotations.extract_from_plain(msg_body)) |     eq_("Test reply", quotations.extract_from_plain(msg_body)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_pattern_sent_from_samsung_smb_wrote(): | ||||||
|  |     msg_body = """Test reply | ||||||
|  |  | ||||||
|  | Sent from Samsung MobileName <address@example.com> wrote: | ||||||
|  |  | ||||||
|  | > | ||||||
|  | > Test | ||||||
|  | > | ||||||
|  | > Roman""" | ||||||
|  |  | ||||||
|  |     eq_("Test reply", quotations.extract_from_plain(msg_body)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_pattern_on_date_wrote_somebody(): | def test_pattern_on_date_wrote_somebody(): | ||||||
|     eq_('Lorem', quotations.extract_from_plain( |     eq_('Lorem', quotations.extract_from_plain( | ||||||
|     """Lorem |     """Lorem | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user