import urllib.request, urllib.parse, json, lxml.html


class NoSuchPageException(Exception):
    pass


class DisambiguationPageException(Exception):
    pass

SEARCH_URL = 'http://en.wikiquote.org/w/api.php?format=json&action=query&list=search&continue=&srsearch='
PAGE_URL = 'http://en.wikiquote.org/w/api.php?format=json&action=parse&prop=text|categories&page='
MIN_QUOTE_LEN = 6
MIN_QUOTE_WORDS = 3
DEFAULT_MAX_QUOTES = 20
WORD_BLACKLIST = ['quoted', 'Variant:', 'Retrieved', 'Notes:']


def json_from_url(url):
    res = urllib.request.urlopen(url)
    body = res.read().decode()
    return json.loads(body)


def search(s):
    search_terms = urllib.parse.quote(s)
    data = json_from_url(SEARCH_URL + search_terms)
    results = [entry['title'] for entry in data['query']['search']]
    return results


def is_disambiguation(categories):
    # Checks to see if at least one category includes 'Disambiguation_pages'
    return not categories or any([category['*'] == 'Disambiguation_pages' for category in categories])


def is_quote(txt):
    txt_split = txt.strip().split()
    invalid_conditions = [
        not txt or not txt[0].isupper() or len(txt) < MIN_QUOTE_LEN,
        len(txt_split) < MIN_QUOTE_WORDS,
        any([True for word in txt_split if word in WORD_BLACKLIST]),
        txt.endswith(('(', ':', ']'))
    ]

    # Returns false if any invalid conditions are true, otherwise returns True.
    return not any(invalid_conditions)


def extract_quotes(html_content, max_quotes):
    tree = lxml.html.fromstring(html_content)
    quotes_list = []

    # List items inside unordered lists
    node_list = tree.xpath('//div/ul/li')

    # Description tags inside description lists (first is generally not a quote)
    dd_list = tree.xpath('//div/dl/dd')[1:]
    if len(dd_list) > len(node_list):
        node_list += dd_list

    for txt in node_list:
        uls = txt.xpath('ul')
        for ul in uls:
            ul.getparent().remove(ul)

        txt = txt.text_content().strip()
        if is_quote(txt) and max_quotes >= len(quotes_list):
            txt_normal = ' '.join(txt.split())
            quotes_list.append(txt_normal)

    return quotes_list


def quotes(page_title, max_quotes=DEFAULT_MAX_QUOTES):
    data = json_from_url(PAGE_URL + urllib.parse.quote(page_title))
    if 'error' in data:
        raise NoSuchPageException('No pages matched the title: ' + page_title)

    if is_disambiguation(data['parse']['categories']):
        raise DisambiguationPageException('Title returned a disambiguation page.')

    html_content = data['parse']['text']['*']
    return extract_quotes(html_content, max_quotes)

@@ -14,17 +14,23 @@
 .request
-,
+%0Aimport
  urllib.
@@ -38,15 +38,27 @@
 arse
-, json,
+%0Aimport json%0Aimport
  lxm
@@ -171,22 +171,17 @@
   pass%0A%0A
-SEARCH
+W
 _URL = '
@@ -205,32 +205,54 @@
 te.org/w/api.php
+'%0ASRCH_URL = W_URL + '
 ?format=json&act
@@ -309,42 +309,17 @@
 L = 
-'http://en.wikiquote.org/w/api.php
+W_URL + '
 ?for
@@ -629,24 +629,56 @@
  search(s):%0A
+    if not s:%0A        return %5B%5D%0A
     search_t
@@ -736,10 +736,8 @@
 rl(S
-EA
 RCH_
@@ -991,16 +991,25 @@
 or any(%5B
+%0A        
 category
@@ -1066,16 +1066,21 @@
 tegories
+%0A    
 %5D)%0A%0A%0Adef
@@ -1079,29 +1079,35 @@
 %5D)%0A%0A%0Adef is_
-quote
+cast_credit
 (txt):%0A    t
@@ -1104,16 +1104,352 @@
 xt):%0A   
+ 
+# Checks to see if the text is a cast credit:%0A    #   %3Cactor name%3E as %3Ccharacter name%3E%0A    #   %3Cactor name%3E - %3Ccharacter name%3E%0A    if len(txt) %3C 3:%0A        return False%0A%0A    conditions = %5B%0A        txt%5B0%5D%5B0%5D.isupper(),%0A        txt%5B1%5D%5B0%5D.isupper(),%0A        txt%5B2%5D in %5B'as', '-'%5D%0A    %5D%0A%0A    return all(conditions)%0A%0A%0Adef is_quote(txt):%0A   
  txt_spl
@@ -1456,24 +1456,16 @@
 it = txt
-.strip()
 .split()
@@ -1709,16 +1709,51 @@
 ', '%5D'))
+,%0A        is_cast_credit(txt_split)
 %0A    %5D%0A%0A
@@ -2121,16 +2121,26 @@
 ists
+,%0A    #
  
-(
 first 
+one 
 is g
@@ -2159,17 +2159,16 @@
  a quote
-)
 %0A    dd_
@@ -2932,16 +2932,56 @@
 ception(
+%0A                                       
 'Title r

fix empty search, cast_credit check, more PEP8 formatting