Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf37090ca | ||
|
|
44fcef7123 | ||
|
|
69a44b10a1 | ||
|
|
b085e3d049 | ||
|
|
4b953bcddc | ||
|
|
315eaa7080 |
2
setup.py
2
setup.py
@@ -29,7 +29,7 @@ class InstallCommand(install):
|
|||||||
|
|
||||||
|
|
||||||
setup(name='talon',
|
setup(name='talon',
|
||||||
version='1.2.14',
|
version='1.2.15',
|
||||||
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,7 +12,7 @@ from copy import deepcopy
|
|||||||
|
|
||||||
from lxml import html, etree
|
from lxml import html, etree
|
||||||
|
|
||||||
from talon.utils import get_delimiter, html_to_text
|
from talon.utils import get_delimiter, html_tree_to_text
|
||||||
from talon import html_quotations
|
from talon import html_quotations
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
import six
|
import six
|
||||||
@@ -391,7 +391,7 @@ def _extract_from_html(msg_body):
|
|||||||
if msg_body.strip() == b'':
|
if msg_body.strip() == b'':
|
||||||
return msg_body
|
return msg_body
|
||||||
|
|
||||||
msg_body = msg_body.replace(b'\r\n', b'').replace(b'\n', b'')
|
msg_body = msg_body.replace(b'\r\n', b'\n')
|
||||||
html_tree = html.document_fromstring(
|
html_tree = html.document_fromstring(
|
||||||
msg_body,
|
msg_body,
|
||||||
parser=html.HTMLParser(encoding="utf-8")
|
parser=html.HTMLParser(encoding="utf-8")
|
||||||
@@ -407,8 +407,7 @@ def _extract_from_html(msg_body):
|
|||||||
|
|
||||||
number_of_checkpoints = html_quotations.add_checkpoint(html_tree, 0)
|
number_of_checkpoints = html_quotations.add_checkpoint(html_tree, 0)
|
||||||
quotation_checkpoints = [False] * number_of_checkpoints
|
quotation_checkpoints = [False] * number_of_checkpoints
|
||||||
msg_with_checkpoints = html.tostring(html_tree)
|
plain_text = html_tree_to_text(html_tree)
|
||||||
plain_text = html_to_text(msg_with_checkpoints)
|
|
||||||
plain_text = preprocess(plain_text, '\n', content_type='text/html')
|
plain_text = preprocess(plain_text, '\n', content_type='text/html')
|
||||||
lines = plain_text.splitlines()
|
lines = plain_text.splitlines()
|
||||||
|
|
||||||
@@ -431,25 +430,31 @@ def _extract_from_html(msg_body):
|
|||||||
return_flags = []
|
return_flags = []
|
||||||
process_marked_lines(lines, markers, return_flags)
|
process_marked_lines(lines, markers, return_flags)
|
||||||
lines_were_deleted, first_deleted, last_deleted = return_flags
|
lines_were_deleted, first_deleted, last_deleted = return_flags
|
||||||
|
|
||||||
|
if not lines_were_deleted and not cut_quotations:
|
||||||
|
return msg_body
|
||||||
|
|
||||||
if lines_were_deleted:
|
if lines_were_deleted:
|
||||||
#collect checkpoints from deleted lines
|
#collect checkpoints from deleted lines
|
||||||
for i in range(first_deleted, last_deleted):
|
for i in range(first_deleted, last_deleted):
|
||||||
for checkpoint in line_checkpoints[i]:
|
for checkpoint in line_checkpoints[i]:
|
||||||
quotation_checkpoints[checkpoint] = True
|
quotation_checkpoints[checkpoint] = True
|
||||||
else:
|
|
||||||
if cut_quotations:
|
|
||||||
return html.tostring(html_tree_copy)
|
|
||||||
else:
|
|
||||||
return msg_body
|
|
||||||
|
|
||||||
# Remove tags with quotation checkpoints
|
# Remove tags with quotation checkpoints
|
||||||
html_quotations.delete_quotation_tags(
|
html_quotations.delete_quotation_tags(
|
||||||
html_tree_copy, 0, quotation_checkpoints
|
html_tree_copy, 0, quotation_checkpoints
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if _readable_text_empty(html_tree_copy):
|
||||||
|
return msg_body
|
||||||
|
|
||||||
return html.tostring(html_tree_copy)
|
return html.tostring(html_tree_copy)
|
||||||
|
|
||||||
|
|
||||||
|
def _readable_text_empty(html_tree):
|
||||||
|
return not bool(html_tree_to_text(html_tree).strip())
|
||||||
|
|
||||||
|
|
||||||
def is_splitter(line):
|
def is_splitter(line):
|
||||||
'''
|
'''
|
||||||
Returns Matcher object if provided string is a splitter and
|
Returns Matcher object if provided string is a splitter and
|
||||||
|
|||||||
@@ -112,25 +112,7 @@ def get_delimiter(msg_body):
|
|||||||
|
|
||||||
return delimiter
|
return delimiter
|
||||||
|
|
||||||
|
def html_tree_to_text(tree):
|
||||||
def html_to_text(string):
|
|
||||||
"""
|
|
||||||
Dead-simple HTML-to-text converter:
|
|
||||||
>>> html_to_text("one<br>two<br>three")
|
|
||||||
>>> "one\ntwo\nthree"
|
|
||||||
|
|
||||||
NOTES:
|
|
||||||
1. the string is expected to contain UTF-8 encoded HTML!
|
|
||||||
2. returns utf-8 encoded str (not unicode)
|
|
||||||
"""
|
|
||||||
if isinstance(string, six.text_type):
|
|
||||||
string = string.encode('utf8')
|
|
||||||
|
|
||||||
s = _prepend_utf8_declaration(string)
|
|
||||||
s = s.replace(b"\n", b"")
|
|
||||||
|
|
||||||
tree = html.fromstring(s)
|
|
||||||
|
|
||||||
for style in CSSSelector('style')(tree):
|
for style in CSSSelector('style')(tree):
|
||||||
style.getparent().remove(style)
|
style.getparent().remove(style)
|
||||||
|
|
||||||
@@ -159,6 +141,26 @@ def html_to_text(string):
|
|||||||
return _encode_utf8(retval)
|
return _encode_utf8(retval)
|
||||||
|
|
||||||
|
|
||||||
|
def html_to_text(string):
|
||||||
|
"""
|
||||||
|
Dead-simple HTML-to-text converter:
|
||||||
|
>>> html_to_text("one<br>two<br>three")
|
||||||
|
>>> "one\ntwo\nthree"
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
1. the string is expected to contain UTF-8 encoded HTML!
|
||||||
|
2. returns utf-8 encoded str (not unicode)
|
||||||
|
"""
|
||||||
|
if isinstance(string, six.text_type):
|
||||||
|
string = string.encode('utf8')
|
||||||
|
|
||||||
|
s = _prepend_utf8_declaration(string)
|
||||||
|
s = s.replace(b"\n", b"")
|
||||||
|
|
||||||
|
tree = html.fromstring(s)
|
||||||
|
return html_tree_to_text(tree)
|
||||||
|
|
||||||
|
|
||||||
def _contains_charset_spec(s):
|
def _contains_charset_spec(s):
|
||||||
"""Return True if the first 4KB contain charset spec
|
"""Return True if the first 4KB contain charset spec
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -356,7 +356,8 @@ def test_CRLF():
|
|||||||
assert_false(symbol in extracted)
|
assert_false(symbol in extracted)
|
||||||
eq_('<html></html>', RE_WHITESPACE.sub('', extracted))
|
eq_('<html></html>', RE_WHITESPACE.sub('', extracted))
|
||||||
|
|
||||||
msg_body = """Reply
|
msg_body = """My
|
||||||
|
reply
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -371,8 +372,8 @@ def test_CRLF():
|
|||||||
msg_body = msg_body.replace('\n', '\r\n')
|
msg_body = msg_body.replace('\n', '\r\n')
|
||||||
extracted = quotations.extract_from_html(msg_body)
|
extracted = quotations.extract_from_html(msg_body)
|
||||||
assert_false(symbol in extracted)
|
assert_false(symbol in extracted)
|
||||||
eq_("<html><body><p>Reply</p></body></html>",
|
# Keep new lines otherwise "My reply" becomes one word - "Myreply"
|
||||||
RE_WHITESPACE.sub('', extracted))
|
eq_("<html><body><p>My\nreply\n</p></body></html>", extracted)
|
||||||
|
|
||||||
|
|
||||||
def test_gmail_forwarded_msg():
|
def test_gmail_forwarded_msg():
|
||||||
@@ -392,3 +393,21 @@ def test_too_large_html():
|
|||||||
'</div>'
|
'</div>'
|
||||||
eq_(RE_WHITESPACE.sub('', msg_body),
|
eq_(RE_WHITESPACE.sub('', msg_body),
|
||||||
RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body)))
|
RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_readable_html_empty():
|
||||||
|
msg_body = """
|
||||||
|
<blockquote>
|
||||||
|
Reply
|
||||||
|
<div>
|
||||||
|
On 11-Apr-2011, at 6:54 PM, Bob <bob@example.com> wrote:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Test
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</blockquote>"""
|
||||||
|
|
||||||
|
eq_(RE_WHITESPACE.sub('', msg_body),
|
||||||
|
RE_WHITESPACE.sub('', quotations.extract_from_html(msg_body)))
|
||||||
|
|||||||
Reference in New Issue
Block a user