text: from . import utils
  import logging
  import re
  import json
  from datetime import datetime
  from lxml import etree
  import time
  from lxml.html import fromstring
  
  # can be run on its own, just require a nomination_id (e.g. PN2094-112)
  
  
  def run(options):
  	nomination_id = options.get('nomination_id', None)
  
  	if nomination_id:
  		result = fetch_nomination(nomination_id, options)
  		logging.warn("\n%s" % result)
  	else:
  		logging.error("To run this task directly, supply a bill_id.")
  
  # download and cache page for nomination
  
  
  def fetch_nomination(nomination_id, options={}):
  	logging.info("\n[%s] Fetching..." % nomination_id)
  
  	# fetch committee name map, if it doesn't already exist
  	nomination_type, number, congress = utils.split_nomination_id(nomination_id)
  	if not number:
  		return {'saved': False, 'ok': False, 'reason': "Couldn't parse %s" % nomination_id}
  
  	if not utils.committee_names:
  		utils.fetch_committee_names(congress, options)
  
  	# fetch bill details body
  	body = utils.download(
  		nomination_url_for(nomination_id),
  		nomination_cache_for(nomination_id, "information.html"), options)
  
  	if not body:
  		return {'saved': False, 'ok': False, 'reason': "failed to download"}
  
  	if options.get("download_only", False):
  		return {'saved': False, 'ok': True, 'reason': "requested download only"}
  
  	# TODO:
  	#   detect group nominations, particularly for military promotions
  	#   detect when a group nomination is split into subnominations
  	#
  	# Also, the splitting process is nonsense:
  	# http://thomas.loc.gov/home/PN/split.htm
  
  	if "split into two or more parts" in body:
  		return {'saved': False, 'ok': True, 'reason': 'was split'}
  
  	nomination = parse_nomination(nomination_id, body, options)
  	output_nomination(nomination, options)
  	return {'ok': True, 'saved': True}
  
  
  def parse_nomination(nomination_id, body, options):
  	nomination_type, number, congress = utils.split_nomination_id(nomination_id)
  
  	# remove (and store) comments, which contain some info for the nomination
  	# but also mess up the parser
  	facts = re.findall("<!--(.+?)-->", body)
  	body = re.sub("<!--.+?-->", "", body)
  
  	doc = fromstring(body)
  
  	# get rid of centered bold labels, they screw stuff up,
  	# e.g. agency names on PN1375-113
  	body = re.sub(re.compile("<div align=\"center\">.+?</div>", re.M), "", body)
  	for elem in doc.xpath('//div[@align="center"]'):
  		elem.getparent().remove(elem)
  
  	committee_names = []
  	committees = []
  
  	info = {
  		'nomination_id': nomination_id, 'actions': []
  	}
  
  	# the markup on these pages is a disaster, so we're going to use a heuristic based on boldface, inline tags followed by text
  	for pair in doc.xpath('//span[@class="elabel"]|//strong'):
  		if pair.tail:
  			text = pair.text or pair.text_content()
  			label, data = text.replace(':', '').strip(), pair.tail.strip()
  
  			# handle actions separately
  			if label.split(" ")[-1] == "Action":
  				pieces = re.split("\s+\-\s+", data)
  
  				location = label.split(" ")[0].lower()
  
  				# use 'acted_at', even though it's always a date, to be consistent
  				# with acted_at field on bills and amendments
  				acted_at = datetime.strptime(pieces[0], "%B %d, %Y").strftime("%Y-%m-%d")
  
  				# join rest back together (in case action itself has a hyphen)
  				text = str.join(" - ", pieces[1:len(pieces)])
  
  				info['actions'].append({
  					"type": "action",
  					"location": location,
  					"acted_at": acted_at,
  					"text": text
  				})
  
  			else:
  				# let's handle these cases one by one
  				if label == "Organization":
  					info["organization"] = data
  
  				elif label == "Control Number":
  					# this doesn't seem useful
  					pass
  
  				elif label.lower() == "referred to":
  					committee_names.append(data)
  
  				elif label == "Reported by":
  					info["reported_by"] = data
  
  				elif label == "Nomination":
  					# sanity check - verify nomination_id matches
  					if nomination_id != data:
  						raise Exception("Whoa! Mismatched nomination ID.")
  
  				elif label == "Date Received":
  					# Note: Will break with the 1000th congress in year 3789
  					match = re.search("(\d{2,3})[stndhr]{2}", data)
  					if match:
  						info["congress"] = int(match.group(1))
  					else:
  						raise Exception("Choked, couldn't find Congress in \"%s\"" % data)
  
  					# Doc format is: "January 04, 1995 (104th Congress)"
  					info["received_on"] = datetime.strptime(data.split(" (")[0], "%B %d, %Y").strftime("%Y-%m-%d")
  
  				elif label == "Nominee":
  
  					# ignore any vice suffix
  					name = data.split(", vice")[0]
  
  					try:
  						name = re.search("(.+?),", name).groups()[0]
  					except Exception as e:
  						raise Exception("Couldn't parse nominee entry: %s" % name)
  
  					# Some begin "One nomination,...", so 'List of Nominees' will get it
  					if "nomination" in name:
  						pass
  
  					# and grab the state and position out of the comment facts
  					if facts[-5]:
  						position = facts[-5]
  					else:
  						raise Exception("Couldn't find the position in the comments.")
  
  					info["nominees"] = [{
  						"name": name,
  						"position": position,
  						"state": facts[-6][2:]
  					}]
  
  				elif label.lower() == "nominees":
  					pass
  
  				elif label.lower() == "authority date":
  					pass
  
  				elif label.lower() == "list of nominees":
  					# step through each sibling, collecting each br's stripped tail for names as we go
  					# stop when we get to a strong or span (next label)
  					nominees = []
  
  					current_position = None
  					for sibling in pair.itersiblings():
  						if sibling.tag == "br":
  							if sibling.tail:
  								name = sibling.tail.strip()
  								if (name[0:5].lower() == "to be"):
  									current_position = name[6:].strip()
  								elif name:
  									nominees.append({
  										"name": sibling.tail.strip(),
  										"position": current_position
  									})
  						elif (sibling.tag == "strong") or (sibling.tag == "span"):
  							break
  
  					info["nominees"] = nominees
  
  				else:
  					# choke, I think we handle all of them now
  					raise Exception("Unrecognized label: %s" % label)
  
  	if not info.get("received_on", None):
  		raise Exception("Choked, couldn't find received date.")
  
  	if not info.get("nominees", None):
  		raise Exception("Choked, couldn't find nominee info.")
  
  	# try to normalize committee name to an ID
  	# choke if it doesn't work - the names should match up.
  	for committee_name in committee_names:
  		committee_id = utils.committee_names[committee_name]
  		committees.append(committee_id)
  	info["referred_to"] = committees
  	info["referred_to_names"] = committee_names
  
  	return info
  
  # directory helpers
  
  
  def output_for_nomination(nomination_id, format):
  	nomination_type, number, congress = utils.split_nomination_id(nomination_id)
  	return "%s/%s/nominations/%s/%s" % (utils.data_dir(), congress, number, "data.%s" % format)
  
  
  def nomination_url_for(nomination_id):
  	nomination_type, number, congress = utils.split_nomination_id(nomination_id)
  
  	# numbers can be either of the form "63" or "64-01"
  	number_pieces = number.split("-")
  	if len(number_pieces) == 1:
  		number_pieces.append("00")
  	url_number = "%05d%s" % (int(number_pieces[0]), number_pieces[1])
  
  	return "http://thomas.loc.gov/cgi-bin/ntquery/z?nomis:%03d%s%s:/" % (int(congress), nomination_type.upper(), url_number)
  
  
  def nomination_cache_for(nomination_id, file):
  	nomination_type, number, congress = utils.split_nomination_id(nomination_id)
  	return "%s/nominations/%s/%s" % (congress, number, file)
  
  
  def output_nomination(nomination, options):
  	logging.info("[%s] Writing to disk..." % nomination['nomination_id'])
  
  	# output JSON - so easy!
  	utils.write(
  		json.dumps(nomination, sort_keys=True, indent=2, default=utils.format_datetime),
  		output_for_nomination(nomination['nomination_id'], "json")
  	)
  