1 | # needed for os specific separators
|
---|
2 | import os
|
---|
3 | # selenium is needed for browser manipulation
|
---|
4 | from selenium import webdriver
|
---|
5 | from selenium.common.exceptions import NoSuchElementException
|
---|
6 | from selenium.common.exceptions import NoSuchWindowException
|
---|
7 | from selenium.webdriver.common.by import By
|
---|
8 | from selenium.webdriver.support.ui import WebDriverWait, Select
|
---|
9 | from selenium.webdriver.support import expected_conditions as EC
|
---|
10 | from selenium.webdriver.common.keys import Keys
|
---|
11 | # urllib is needed to obtain the xml
|
---|
12 | import urllib2
|
---|
13 | # xmlparser
|
---|
14 | from lxml import etree
|
---|
15 | from StringIO import StringIO
|
---|
16 | import lxml.html
|
---|
17 | import time
|
---|
18 |
|
---|
19 |
|
---|
20 | class MavaSimbriefIntegrator():
|
---|
21 | """Implements the integration with the excellent Simbrief pilot briefing
|
---|
22 | system for MALEV Virtual."""
|
---|
23 |
|
---|
24 | def __init__(self,
|
---|
25 | plan,
|
---|
26 | driver=None,
|
---|
27 | simbrief_query_settings=None,
|
---|
28 | mava_simbrief_url=None,
|
---|
29 | xml_link_fix_part=None):
|
---|
30 | """Init the integrator with settings that are typical of our use.
|
---|
31 | @param: plan - flightplan dictionary
|
---|
32 | @param: webdriver - a selenium webdriver
|
---|
33 | @param: simbrief_query_settings - a dictionary of query settings
|
---|
34 | @param: mava_simbrief_url - url to the form that is sent to simbrief
|
---|
35 | on the mava server
|
---|
36 | @param: xml_link_fix_part = url to the simbrief website under which the
|
---|
37 | xml is to be found"""
|
---|
38 | self.plan = plan
|
---|
39 | if simbrief_query_settings is None:
|
---|
40 | self.simbrief_query_settings = {
|
---|
41 | 'navlog': True,
|
---|
42 | 'etops': True,
|
---|
43 | 'stepclimbs': True,
|
---|
44 | 'tlr': True,
|
---|
45 | 'notams': True,
|
---|
46 | 'firnot': True,
|
---|
47 | 'maps': 'Simple',
|
---|
48 | }
|
---|
49 | else:
|
---|
50 | self.simbrief_query_settings = simbrief_query_settings
|
---|
51 |
|
---|
52 | if driver is None:
|
---|
53 | self.driver = webdriver.Firefox()
|
---|
54 | else:
|
---|
55 | self.driver = driver
|
---|
56 |
|
---|
57 | if mava_simbrief_url is None:
|
---|
58 | self.mava_simbrief_url = "http://flare.privatedns.org/" \
|
---|
59 | "mava_simbrief/simbrief_form.html"
|
---|
60 | else:
|
---|
61 | self.mava_simbrief_url = mava_simbrief_url
|
---|
62 |
|
---|
63 | if xml_link_fix_part is None:
|
---|
64 | self.xml_link_fix_part = "http://www.simbrief.com/ofp/" \
|
---|
65 | "flightplans/xml/"
|
---|
66 | else:
|
---|
67 | self.xml_link_fix_part = xml_link_fix_part
|
---|
68 |
|
---|
69 | def fill_form(self,
|
---|
70 | plan,
|
---|
71 | simbrief_query_settings):
|
---|
72 | """Fills the form of the webpage using the paramteres that the class
|
---|
73 | has been initialized with.
|
---|
74 | @param: driver - a selenium webdriver
|
---|
75 | @param: plan - dictionary containing plan details
|
---|
76 | @param: simbrief_query_settings - dictionary containing plan settings"""
|
---|
77 | for plan_input_field in plan.iterkeys():
|
---|
78 | self.driver.find_element_by_name(plan_input_field).send_keys(
|
---|
79 | plan[plan_input_field])
|
---|
80 | for option_checkbox in simbrief_query_settings.iterkeys():
|
---|
81 | if (isinstance(simbrief_query_settings[option_checkbox], bool) and
|
---|
82 | simbrief_query_settings[option_checkbox]):
|
---|
83 | # if setting is a boolean type and true
|
---|
84 | self.driver.find_element_by_name(option_checkbox).click()
|
---|
85 | elif isinstance(simbrief_query_settings[option_checkbox], str):
|
---|
86 | # if setting is a select
|
---|
87 | Select(self.driver.find_element_by_name(option_checkbox)).\
|
---|
88 | select_by_visible_text(simbrief_query_settings[
|
---|
89 | option_checkbox])
|
---|
90 |
|
---|
91 | def get_xml_link(self,
|
---|
92 | get_credentials, update_progress,
|
---|
93 | local_xml_debug=False,
|
---|
94 | local_html_debug=False):
|
---|
95 | """Obtains the link of the xml to be processed.
|
---|
96 | @param get_credentials - a function, which should return a pair of the
|
---|
97 | user name and password to log in to SimBrief. It gets an integer which
|
---|
98 | is the number of times it has been called so far.
|
---|
99 | @param local_xml_debug - if True then not the real location will be
|
---|
100 | checked but the current working dir will be searched for 'xml.xml'
|
---|
101 | @param local_html_debug - if True then not real mava_simbrief_url will
|
---|
102 | be used but the current working dir will be searched for
|
---|
103 | 'mava_simbrief.html'
|
---|
104 | @returns: the string of the xml link if it could be obtained and None
|
---|
105 | otherwise"""
|
---|
106 | # Finding xml_link
|
---|
107 | is_briefing_available = False
|
---|
108 | if local_xml_debug:
|
---|
109 | # set link for locally debugging an existing xml file
|
---|
110 | xml_link = ("file://"
|
---|
111 | + os.getcwd()
|
---|
112 | + os.sep
|
---|
113 | + "xml.xml")
|
---|
114 | is_briefing_available = True
|
---|
115 | else:
|
---|
116 | update_progress("Looking for a browser...", False)
|
---|
117 | # There must be window whose title is 'SimBrief' so that we
|
---|
118 | # could find our one among several
|
---|
119 | if self._find_window_by_title("SimBrief") is None:
|
---|
120 | print "No SimBrief window was found!"
|
---|
121 | return None
|
---|
122 |
|
---|
123 | # normal operation with a real xml file
|
---|
124 | update_progress("Retrieving form...", False)
|
---|
125 | if local_html_debug:
|
---|
126 | self.driver.get(
|
---|
127 | "file://" + os.getcwd() + os.sep + "simbrief_form.html")
|
---|
128 | is_briefing_available = True
|
---|
129 | else:
|
---|
130 | self.driver.get(self.mava_simbrief_url)
|
---|
131 |
|
---|
132 | main_handle = self._find_window_by_title("Malev Virtual Simbrief Integration System")
|
---|
133 | if main_handle is None:
|
---|
134 | print "No SimBrief Integration window was found!"
|
---|
135 | return None
|
---|
136 |
|
---|
137 | # Make a copy of the window handles before submitting the form,
|
---|
138 | # so that we could find the popup
|
---|
139 | handles = self.driver.window_handles[:]
|
---|
140 |
|
---|
141 | # Entering form data
|
---|
142 | update_progress("Filling form...", False)
|
---|
143 | self.driver.switch_to_window(main_handle)
|
---|
144 | self.fill_form(self.plan,
|
---|
145 | self.simbrief_query_settings)
|
---|
146 |
|
---|
147 | # Loading page
|
---|
148 | button = self.driver.find_element_by_name("submitform")
|
---|
149 | button.send_keys(Keys.RETURN)
|
---|
150 |
|
---|
151 | update_progress("Waiting for login...", False)
|
---|
152 | popup_handle = self._find_popup(handles)
|
---|
153 | if popup_handle is None:
|
---|
154 | print "No popup window was found!"
|
---|
155 | return None
|
---|
156 |
|
---|
157 | login_count = 0
|
---|
158 | end_time = time.time() + 120.0
|
---|
159 | while not is_briefing_available and end_time > time.time():
|
---|
160 | try:
|
---|
161 | self.driver.switch_to.window(popup_handle)
|
---|
162 |
|
---|
163 | userElement = self.driver.find_element_by_name("user")
|
---|
164 |
|
---|
165 | if userElement is not None:
|
---|
166 | update_progress("Logging in...", False)
|
---|
167 | (userName, password) = get_credentials(login_count)
|
---|
168 | userElement.send_keys(userName)
|
---|
169 | self.driver.find_element_by_name("pass").send_keys(password)
|
---|
170 | self.driver.find_element_by_name("staylogged").click()
|
---|
171 |
|
---|
172 | self.driver.find_element(By.XPATH,
|
---|
173 | "//input[@value='Login']").send_keys(Keys.RETURN)
|
---|
174 | login_count += 1
|
---|
175 | except NoSuchElementException:
|
---|
176 | pass
|
---|
177 | except NoSuchWindowException:
|
---|
178 | pass
|
---|
179 |
|
---|
180 | update_progress("Waiting for the result...", False)
|
---|
181 | self.driver.switch_to.window(main_handle)
|
---|
182 | try:
|
---|
183 | if self.driver.find_element_by_name("hidden_is_briefing_available") is not None:
|
---|
184 | is_briefing_available = True
|
---|
185 | xml_link_element = self.driver.find_element_by_name(
|
---|
186 | 'hidden_link')
|
---|
187 | xml_link_generated_part = xml_link_element.get_attribute(
|
---|
188 | 'value')
|
---|
189 | xml_link = self.xml_link_fix_part + xml_link_generated_part + '.xml'
|
---|
190 | print(xml_link)
|
---|
191 | except NoSuchElementException:
|
---|
192 | pass
|
---|
193 |
|
---|
194 | self.driver.quit()
|
---|
195 |
|
---|
196 | if is_briefing_available:
|
---|
197 | return xml_link
|
---|
198 | else:
|
---|
199 | return None
|
---|
200 |
|
---|
201 | def get_results(self,
|
---|
202 | xml_link,
|
---|
203 | html_file_path = None):
|
---|
204 | """Parses the xml for information.
|
---|
205 | @param xml_link - a path to the xml file
|
---|
206 | @return a dictionary of the found information"""
|
---|
207 | # Setup variables
|
---|
208 | ## Holds analysis data not used
|
---|
209 | available_info = {}
|
---|
210 | ## Holds analysis data to be used
|
---|
211 | flight_info = {}
|
---|
212 | ## Holds notams
|
---|
213 | notams_list = []
|
---|
214 | ## Notam counter
|
---|
215 | i = 0
|
---|
216 | # Obtaining the xml
|
---|
217 | response = urllib2.urlopen(xml_link)
|
---|
218 | xml_content = response.read()
|
---|
219 | # Processing xml
|
---|
220 | tree = etree.parse(StringIO(xml_content))
|
---|
221 | context = etree.iterparse(StringIO(xml_content))
|
---|
222 | for action, element in context:
|
---|
223 | # Processing tags that occur multiple times
|
---|
224 | ## NOTAMS
|
---|
225 | if element.tag == 'notamdrec':
|
---|
226 | notams_element_list = list(element)
|
---|
227 | notam_dict = {}
|
---|
228 | for notam in notams_element_list:
|
---|
229 | notam_dict[notam.tag] = notam.text
|
---|
230 | notams_list.append(notam_dict)
|
---|
231 | i += 1
|
---|
232 | ## WEATHER
|
---|
233 | elif element.tag == 'weather':
|
---|
234 | weather_element_list = list(element)
|
---|
235 | for weather in weather_element_list:
|
---|
236 | flight_info[weather.tag] = weather.text
|
---|
237 | else:
|
---|
238 | available_info[element.tag] = element.text
|
---|
239 | # Processing plan_html
|
---|
240 | ## Obtaining chart links
|
---|
241 | image_links = []
|
---|
242 | for image_link_a_element in lxml.html.find_class(
|
---|
243 | available_info['plan_html'], 'ofpmaplink'):
|
---|
244 | for image_link_tuple in image_link_a_element.iterlinks():
|
---|
245 | if image_link_tuple[1] == 'src':
|
---|
246 | image_links.append(image_link_tuple[2])
|
---|
247 | flight_info['image_links'] = image_links
|
---|
248 | print(sorted(available_info.keys()))
|
---|
249 | if html_file_path is None:
|
---|
250 | html_file_path = 'simbrief_plan.html'
|
---|
251 | with open(html_file_path, 'w') as f:
|
---|
252 | f.write(available_info['plan_html'])
|
---|
253 | return flight_info
|
---|
254 |
|
---|
255 | def _find_window_by_title(self, title, timeout = 10.0):
|
---|
256 | """Find the window with the given title.
|
---|
257 |
|
---|
258 | Switch to that window and return its handle."""
|
---|
259 | def predicate(handle):
|
---|
260 | self.driver.switch_to.window(handle)
|
---|
261 | return self.driver.title == title
|
---|
262 |
|
---|
263 | return self._find_window(predicate, timeout = timeout)
|
---|
264 |
|
---|
265 | def _find_popup(self, handles, timeout = 10.0):
|
---|
266 | """Find a popup, i.e. a new window being created.
|
---|
267 |
|
---|
268 | handles is a list of window handles that existed before the popup was
|
---|
269 | expected to be created. If a new handle is found, that is assumed to be
|
---|
270 | the popup window."""
|
---|
271 | return self._find_window(lambda handle: handle not in handles,
|
---|
272 | timeout = timeout)
|
---|
273 |
|
---|
274 | def _find_window(self, predicate, timeout = 10.0):
|
---|
275 | """Find a window that fulfills the given predicate."""
|
---|
276 | window_handle = None
|
---|
277 | end_time = time.time() + timeout
|
---|
278 | while window_handle is None and end_time > time.time():
|
---|
279 | for handle in self.driver.window_handles:
|
---|
280 | if predicate(handle):
|
---|
281 | window_handle = handle
|
---|
282 | break
|
---|
283 |
|
---|
284 | return window_handle
|
---|
285 |
|
---|
286 | if __name__ == "__main__":
|
---|
287 | mava_simbrief_url = "http://flare.privatedns.org/" \
|
---|
288 | "mava_simbrief/simbrief_form.html"
|
---|
289 | xml_link_fix_part = "http://www.simbrief.com/ofp/" \
|
---|
290 | "flightplans/xml/"
|
---|
291 | plan = {
|
---|
292 | 'airline': 'MAH',
|
---|
293 | 'fltnum': '764',
|
---|
294 | 'type': 'B738',
|
---|
295 | 'orig': 'LHBP',
|
---|
296 | 'dest': 'LSZH',
|
---|
297 | 'date': '25FEB15',
|
---|
298 | 'deph': '20',
|
---|
299 | 'depm': '00',
|
---|
300 | 'route': 'GILEP DCT ARSIN UL851 SITNI UL856 NEGRA',
|
---|
301 | 'steh': '22',
|
---|
302 | 'stem': '05',
|
---|
303 | 'reg': 'HA-LOC',
|
---|
304 | 'fin': 'LOC',
|
---|
305 | 'selcal': 'XXXX',
|
---|
306 | 'pax': '100',
|
---|
307 | 'altn': 'LSGG',
|
---|
308 | 'fl': '36000',
|
---|
309 | 'cpt': 'BALINT SZEBENYI',
|
---|
310 | 'pid': 'P008',
|
---|
311 | 'fuelfactor': 'P000',
|
---|
312 | 'manualzfw': '42.7',
|
---|
313 | 'addedfuel': '2.5',
|
---|
314 | 'contpct': '0.05',
|
---|
315 | 'resvrule': '45',
|
---|
316 | 'taxiout': '10',
|
---|
317 | 'taxiin': '4',
|
---|
318 | 'cargo': '5.0',
|
---|
319 | 'origrwy': '31L',
|
---|
320 | 'destrwy': '34',
|
---|
321 | 'climb': '250/300/78',
|
---|
322 | 'descent': '80/280/250',
|
---|
323 | 'cruise': 'LRC',
|
---|
324 | 'civalue': 'AUTO',
|
---|
325 | }
|
---|
326 | integrator = MavaSimbriefIntegrator(plan=plan)
|
---|
327 | link = integrator.get_xml_link(local_xml_debug=False,
|
---|
328 | local_html_debug=False)
|
---|
329 | flight_info = integrator.get_results(link)
|
---|
330 | print(flight_info)
|
---|
331 |
|
---|