def parse_scanner_condition(scond): c = wsd_scan__structures.ScannerCondition() c.id = int(scond.get("Id")) c.time = wsd_common.xml_find(scond, ".//sca:Time").text c.name = wsd_common.xml_find(scond, ".//sca:Name").text c.component = wsd_common.xml_find(scond, ".//sca:Component").text c.severity = wsd_common.xml_find(scond, ".//sca:Severity").text return c
def parse_scan_description(sca_descr): description = wsd_scan__structures.ScannerDescription() description.name = wsd_common.xml_find(sca_descr, ".//sca:ScannerName").text q = wsd_common.xml_find(sca_descr, ".//sca:ScannerInfo") if q is not None: description.info = q.text q = wsd_common.xml_find(sca_descr, ".//sca:ScannerLocation") if q is not None: description.location = q.text return description
def parse_scan_ticket(std_ticket): st = wsd_scan__structures.ScanTicket() st.job_name = wsd_common.xml_find(std_ticket, ".//sca:JobDescription/sca:JobName").text st.job_user_name = wsd_common.xml_find( std_ticket, ".//sca:JobDescription/sca:JobOriginatingUserName").text q = wsd_common.xml_find(std_ticket, ".//sca:JobDescription/sca:JobInformation") if q is not None: st.job_info = q.text dps = wsd_common.xml_find(std_ticket, ".//sca:DocumentParameters") st.doc_params = parse_document_params(dps) return st
def handle_scanner_status_condition_cleared_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## SCANNER STATUS CONDITION CLEARED EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) cond = wsd_common.xml_find(xml_tree, ".//sca:DeviceConditionCleared") cond_id = int(wsd_common.xml_find(cond, ".//sca:ConditionId").text) clear_time = wsd_common.xml_find(cond, ".//sca:ConditionClearTime").text queues.sc_cond_clr_q.put((cond_id, clear_time))
def wsd_scan_available_event_subscribe( hosted_scan_service: wsd_transfer__structures.HostedService, display_str: str, context_str: str, notify_addr: str, expiration: typing.Union[datetime, timedelta] = None): """ Subscribe to ScanAvailable events. :param hosted_scan_service: the wsd service to receive event notifications from :param display_str: the string to display on the device control panel :param context_str: a string internally used to identify the selection of this wsd host as target of the scan :param notify_addr: The address to send notifications to. :param expiration: Expiration time, as a datetime or timedelta object :return: a subscription ID and the token needed in CreateScanJob to start a device-initiated scan, \ or False if a fault message is received instead """ if expiration is None: pass elif expiration.__class__ == "datetime.datetime": expiration = xml_helpers.fmt_as_xml_datetime(expiration) elif expiration.__class__ == "datetime.timedelta": expiration = xml_helpers.fmt_as_xml_duration(expiration) else: raise TypeError expiration_tag = "" if expiration is not None: expiration_tag = "<wse:Expires>%s</wse:Expires>" % expiration fields_map = { "FROM": wsd_globals.urn, "TO": hosted_scan_service.ep_ref_addr, "NOTIFY_ADDR": notify_addr, "OPT_EXPIRATION": expiration_tag, "DISPLAY_STR": display_str, "CONTEXT": context_str } try: x = wsd_common.submit_request( {hosted_scan_service.ep_ref_addr}, "ws-scan__scan_available_event_subscribe.xml", fields_map) dest_token = wsd_common.xml_find(x, ".//sca:DestinationToken").text subscription_id = wsd_common.xml_find(x, ".//wse:Identifier").text return subscription_id, dest_token except TimeoutError: return False
def wsd_get_status(hosted_service: wsd_transfer__structures.HostedService, subscription_id: str) \ -> typing.Union[None, bool, datetime]: """ Get the status of an events subscription of a wsd service :param hosted_service: the wsd service from which you want to hear about the subscription status :type hosted_service: wsd_transfer__structures.HostedService :param subscription_id: the ID returned from a previous successful event subscription call :type subscription_id: str :return: False if a fault message is received instead, \ none if the subscription has no expiration set, \ the expiration date otherwise :rtype: None | False | datetime """ fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "SUBSCRIPTION_ID": subscription_id } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__get_status.xml", fields_map) if wsd_common.check_fault(x): return False e = wsd_common.xml_find(x, ".//wse:Expires") return xml_helpers.parse_xml_datetime(e.text.replace(" ", ""), weak=True) if e is not None else None
def handle_scan_available_event(xml_tree): if wsd_globals.debug is True: print('##\n## SCAN AVAILABLE EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) client_context = wsd_common.xml_find(xml_tree, ".//sca:ClientContext").text scan_identifier = wsd_common.xml_find(xml_tree, ".//sca:ScanIdentifier").text t = threading.Thread( target=device_initiated_scan_worker, args=(client_context, scan_identifier, "scan-" + datetime.now().strftime("%Y-%m-%d_%H_%M_%S"))) t.start()
def handle_scanner_status_summary_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## SCANNER STATUS SUMMARY EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) state = wsd_common.xml_find(xml_tree, ".//sca:ScannerState").text reasons = [] q = wsd_common.xml_find(xml_tree, ".//sca:ScannerStateReasons") if q is not None: dsr = wsd_common.xml_findall(q, ".//sca:ScannerStateReason") for sr in dsr: reasons.append(sr.text) queues.sc_stat_sum_q.put((state, reasons))
def get_sequence(xml_tree: etree.ElementTree) -> typing.List[int]: q = wsd_common.xml_find(xml_tree, ".//wsd:AppSequence") seq = [0, 0, 0] seq[0] = int(q.attrib['InstanceId']) if 'SequenceId' in q.attrib: seq[1] = int(q.attrib['SequenceId']) seq[2] = int(q.attrib['MessageNumber']) return seq
def handle_job_status_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## JOB STATUS EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) s = wsd_common.xml_find(xml_tree, ".//sca:JobStatus") queues.sc_job_status_q.put(wsd_scan__parsers.parse_job_status(s))
def handle_job_end_state_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## JOB END STATE EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) s = wsd_common.xml_find(xml_tree, ".//sca:JobEndState") queues.sc_job_ended_q.put(wsd_scan__parsers.parse_job_summary(s))
def handle_scanner_elements_change_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## SCANNER ELEMENTS CHANGE EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) sca_config = wsd_common.xml_find(xml_tree, ".//sca:ScannerConfiguration") sca_descr = wsd_common.xml_find(xml_tree, ".//sca:ScannerDescription") std_ticket = wsd_common.xml_find(xml_tree, ".//sca:DefaultScanTicket") description = wsd_scan__parsers.parse_scan_description(sca_descr) configuration = wsd_scan__parsers.parse_scan_configuration(sca_config) std_ticket = wsd_scan__parsers.parse_scan_ticket(std_ticket) queues.sc_descr_q.put(description) queues.sc_conf_q.put(configuration) queues.sc_ticket_q.put(std_ticket)
def handle_scanner_status_condition_event(queues, xml_tree): if wsd_globals.debug is True: print('##\n## SCANNER STATUS CONDITION EVENT\n##\n') print( etree.tostring(xml_tree, pretty_print=True, xml_declaration=True)) cond = wsd_common.xml_find(xml_tree, ".//sca:DeviceCondition") cond = wsd_scan__parsers.parse_scanner_condition(cond) queues.sc_cond_q.put(cond)
def parse_scan_status(sca_status): status = wsd_scan__structures.ScannerStatus() status.time = wsd_common.xml_find(sca_status, ".//sca:ScannerCurrentTime").text status.state = wsd_common.xml_find(sca_status, ".//sca:ScannerState").text ac = wsd_common.xml_find(sca_status, ".//sca:ActiveConditions") if ac is not None: dcl = wsd_common.xml_findall(ac, ".//sca:DeviceCondition") for dc in dcl: c = parse_scanner_condition(dc) status.active_conditions[c.id] = c q = wsd_common.xml_find(sca_status, ".//sca:ScannerStateReasons") if q is not None: dsr = wsd_common.xml_findall(q, ".//sca:ScannerStateReason") for sr in dsr: status.reasons.append(sr.text) q = wsd_common.xml_find(sca_status, ".//sca:ConditionHistory") if q is not None: chl = wsd_common.xml_findall(q, ".//sca:ConditionHistoryEntry") for che in chl: c = parse_scanner_condition(che) status.conditions_history[wsd_common.xml_find( che, ".//sca:ClearTime").text] = c return status
def wsd_subscribe(hosted_service: wsd_transfer__structures.HostedService, event_uri: str, notify_addr: str, expiration: typing.Union[datetime, timedelta] = None) \ -> typing.Union[etree.ElementTree, bool]: """ Subscribe to a certain type of events of a wsd service :param hosted_service: the wsd service to receive event notifications from :type hosted_service: wsd_transfer__structures.HostedService :param event_uri: the full URI of the targeted event class. \ Those URIs are taken from ws specifications :type event_uri: str :param notify_addr: The address to send notifications to. :type notify_addr: str :param expiration: Expiration time, as a datetime or timedelta object :type expiration: datetime | timedelta | None :return: the xml SubscribeResponse of the wsd service\ or False if a fault message is received instead :rtype: lxml.etree.ElementTree | False """ if expiration is None: pass elif isinstance(expiration, datetime): expiration = xml_helpers.fmt_as_xml_datetime(expiration) elif isinstance(expiration, timedelta): expiration = xml_helpers.fmt_as_xml_duration(expiration) else: raise TypeError("Type %s not allowed" % expiration.__class__) expiration_tag = "" if expiration is not None: expiration_tag = "<wse:Expires>%s</wse:Expires>" % expiration fields_map = { "FROM": wsd_globals.urn, "TO": hosted_service.ep_ref_addr, "NOTIFY_ADDR": notify_addr, "EXPIRES": expiration, "FILTER_DIALECT": "http://schemas.xmlsoap.org/ws/2006/02/devprof/Action", "EVENT": event_uri, "OPT_EXPIRATION": expiration_tag } x = wsd_common.submit_request({hosted_service.ep_ref_addr}, "ws-eventing__subscribe.xml", fields_map) if wsd_common.check_fault(x): return False return wsd_common.xml_find(x, ".//wse:SubscribeResponse")
def wsd_scanner_status_condition_subscribe(hosted_scan_service: wsd_transfer__structures.HostedService, notify_addr: str, expiration: typing.Union[datetime, timedelta] = None) \ -> typing.Union[bool, str]: """ Subscribe to ScannerStatusCondition events. :param hosted_scan_service: the wsd service to receive event notifications from :param expiration: Expiration time, as a datetime or timedelta object :param notify_addr: The address to send notifications to. :return: False if a fault message is received, a subscription ID otherwise """ event_uri = "http://schemas.microsoft.com/windows/2006/08/wdp/scan/ScannerStatusConditionEvent" x = wsd_eventing__operations.wsd_subscribe(hosted_scan_service, event_uri, notify_addr, expiration) if x is False: return False return wsd_common.xml_find(x, ".//wse:Identifier").text
def parse_media_side(ms): s = wsd_scan__structures.MediaSide() r = wsd_common.xml_find(ms, ".//sca:ScanRegion") if r is not None: q = wsd_common.xml_find(r, ".//sca:ScanRegionXOffset") if q is not None: s.offset = (int(q.text), s.offset[1]) q = wsd_common.xml_find(r, ".//sca:ScanRegionYOffset") if q is not None: s.offset = (s.offset[0], int(q.text)) v1 = wsd_common.xml_find(r, ".//sca:ScanRegionWidth") v2 = wsd_common.xml_find(r, ".//sca:ScanRegionHeight") s.size = (int(v1.text), int(v2.text)) q = wsd_common.xml_find(ms, ".//sca:ColorProcessing") if q is not None: s.color = q.text q = wsd_common.xml_find(ms, ".//sca:Resolution/sca:Width") s.res = (int(q.text), s.res[1]) q = wsd_common.xml_find(ms, ".//sca:Resolution/sca:Height") s.res = (s.res[0], int(q.text)) return s
def do_POST(self): context = self.server.context # request_path = self.path request_headers = self.headers length = int(request_headers["content-length"]) message = self.rfile.read(length) self.protocol_version = "HTTP/1.1" self.send_response(202) self.send_header("Content-Type", "application/soap+xml") self.send_header("Content-Length", "0") self.send_header("Connection", "close") self.end_headers() x = etree.fromstring(message) action = wsd_common.xml_find(x, ".//wsa:Action").text (prefix, _, action) = action.rpartition('/') if prefix != 'http://schemas.microsoft.com/windows/2006/08/wdp/scan': return if action == 'ScanAvailableEvent': self.handle_scan_available_event(x) elif action == 'ScannerElementsChangeEvent': self.handle_scanner_elements_change_event(context['queues'], x) elif action == 'ScannerStatusSummaryEvent': self.handle_scanner_status_summary_event(context['queues'], x) elif action == 'ScannerStatusConditionEvent': self.handle_scanner_status_condition_event(context['queues'], x) elif action == 'ScannerStatusConditionClearedEvent': self.handle_scanner_status_condition_cleared_event( context['queues'], x) elif action == 'JobStatusEvent': self.handle_job_status_event(context['queues'], x) elif action == 'JobEndStateEvent': self.handle_job_end_state_event(context['queues'], x)
def parse_job_status(q): jstatus = wsd_scan__structures.JobStatus() jstatus.id = int(wsd_common.xml_find(q, "sca:JobId").text) q1 = wsd_common.xml_find(q, "sca:JobState") q2 = wsd_common.xml_find(q, "sca:JobCompletedState") jstatus.state = q1.text if q1 is not None else q2.text jstatus.reasons = [ x.text for x in wsd_common.xml_findall(q, "sca:JobStateReasons") ] jstatus.scans_completed = int( wsd_common.xml_find(q, "sca:ScansCompleted").text) a = wsd_common.xml_find(q, "sca:JobCreatedTime") jstatus.creation_time = q.text if a is not None else "" a = wsd_common.xml_find(q, "sca:JobCompletedTime") jstatus.completed_time = q.text if a is not None else "" return jstatus
def parse_scanner_source_settings(se, name): sss = wsd_scan__structures.ScannerSourceSettings() v1 = wsd_common.xml_find(se, ".//sca:%sOpticalResolution/sca:Width" % name) v2 = wsd_common.xml_find(se, ".//sca:%sOpticalResolution/sca:Height" % name) sss.optical_res = (int(v1.text), int(v2.text)) q = wsd_common.xml_findall( se, ".//sca:%sResolutions/sca:Widths/sca:Width" % name) sss.width_res = [x.text for x in q] q = wsd_common.xml_findall( se, ".//sca:%sResolutions/sca:Heights/sca:Height" % name) sss.height_res = [x.text for x in q] q = wsd_common.xml_findall(se, ".//sca:%sColor/sca:ColorEntry" % name) sss.color_modes = [x.text for x in q] v1 = wsd_common.xml_find(se, ".//sca:%sMinimumSize/sca:Width" % name) v2 = wsd_common.xml_find(se, ".//sca:%sMinimumSize/sca:Height" % name) sss.min_size = (int(v1.text), int(v2.text)) v1 = wsd_common.xml_find(se, ".//sca:%sMaximumSize/sca:Width" % name) v2 = wsd_common.xml_find(se, ".//sca:%sMaximumSize/sca:Height" % name) sss.max_size = (int(v1.text), int(v2.text)) return sss
def parse_job_summary(y): jsum = wsd_scan__structures.JobSummary() jsum.name = wsd_common.xml_find(y, "sca:JobName").text jsum.user_name = wsd_common.xml_find(y, "sca:JobOriginatingUserName").text jsum.status = parse_job_status(y) return jsum
def parse_scan_configuration(sca_config): config = wsd_scan__structures.ScannerConfiguration() ds = wsd_common.xml_find(sca_config, ".//sca:DeviceSettings") pla = wsd_common.xml_find(sca_config, ".//sca:Platen") adf = wsd_common.xml_find(sca_config, ".//sca:ADF") # .//sca:Film omitted s = wsd_scan__structures.ScannerSettings() q = wsd_common.xml_findall(ds, ".//sca:FormatsSupported/sca:FormatValue") s.formats = [x.text for x in q] v1 = wsd_common.xml_find( ds, ".//sca:CompressionQualityFactorSupported/sca:MinValue") v2 = wsd_common.xml_find( ds, ".//sca:CompressionQualityFactorSupported/sca:MaxValue") s.compression_factor = (int(v1.text), int(v2.text)) q = wsd_common.xml_findall( ds, ".//sca:ContentTypesSupported/sca:ContentTypeValue") s.content_types = [x.text for x in q] q = wsd_common.xml_find(ds, ".//sca:DocumentSizeAutoDetectSupported") s.size_autodetect_sup = True if q.text == 'true' or q.text == '1' else False q = wsd_common.xml_find(ds, ".//sca:AutoExposureSupported") s.auto_exposure_sup = True if q.text == 'true' or q.text == '1' else False q = wsd_common.xml_find(ds, ".//sca:BrightnessSupported") s.brightness_sup = True if q.text == 'true' or q.text == '1' else False q = wsd_common.xml_find(ds, ".//sca:ContrastSupported") s.contrast_sup = True if q.text == 'true' or q.text == '1' else False v1 = wsd_common.xml_find( ds, ".//sca:ScalingRangeSupported/sca:ScalingWidth/sca:MinValue") v2 = wsd_common.xml_find( ds, ".//sca:ScalingRangeSupported/sca:ScalingWidth/sca:MaxValue") s.scaling_range_w = (int(v1.text), int(v2.text)) v1 = wsd_common.xml_find( ds, ".//sca:ScalingRangeSupported/sca:ScalingHeight/sca:MinValue") v2 = wsd_common.xml_find( ds, ".//sca:ScalingRangeSupported/sca:ScalingHeight/sca:MaxValue") s.scaling_range_h = (int(v1.text), int(v2.text)) q = wsd_common.xml_findall(ds, ".//sca:RotationsSupported/sca:RotationValue") s.rotations = [x.text for x in q] config.settings = s if pla is not None: config.platen = parse_scanner_source_settings(pla, "Platen") if adf is not None: q = wsd_common.xml_find(adf, ".//sca:ADFSupportsDuplex") config.adf_duplex = True if q.text == 'true' or q.text == '1' else False f = wsd_common.xml_find(adf, ".//sca:ADFFront") bk = wsd_common.xml_find(adf, ".//sca:ADFBack") if f is not None: config.front_adf = parse_scanner_source_settings(f, "ADF") if bk is not None: config.back_adf = parse_scanner_source_settings(bk, "ADF") return config
def parse_scan_job(x): scnj = wsd_scan__structures.ScanJob() scnj.id = int(wsd_common.xml_find(x, ".//sca:JobId").text) scnj.token = wsd_common.xml_find(x, ".//sca:JobToken").text q = wsd_common.xml_find(x, ".//sca:ImageInformation/sca:MediaFrontImageInfo") scnj.f_pixel_line = int(wsd_common.xml_find(q, "sca:PixelsPerLine").text) scnj.f_num_lines = int(wsd_common.xml_find(q, "sca:NumberOfLines").text) scnj.f_byte_line = int(wsd_common.xml_find(q, "sca:BytesPerLine").text) q = wsd_common.xml_find(x, ".//sca:ImageInformation/sca:MediaBackImageInfo") if q is not None: scnj.b_pixel_line = int( wsd_common.xml_find(q, "sca:PixelsPerLine").text) scnj.b_num_lines = int( wsd_common.xml_find(q, "sca:NumberOfLines").text) scnj.b_byte_line = int(wsd_common.xml_find(q, "sca:BytesPerLine").text) dpf = wsd_common.xml_find(x, ".//sca:DocumentFinalParameters") scnj.doc_params = parse_document_params(dpf) return scnj
def parse_document_params(dps): dest = wsd_scan__structures.DocumentParams() q = wsd_common.xml_find(dps, ".//sca:Format") if q is not None: dest.format = q.text q = wsd_common.xml_find(dps, ".//sca:CompressionQualityFactor") if q is not None: dest.compression_factor = q.text q = wsd_common.xml_find(dps, ".//sca:ImagesToTransfer") if q is not None: dest.images_num = int(q.text) q = wsd_common.xml_find(dps, ".//sca:InputSource") if q is not None: dest.input_src = q.text q = wsd_common.xml_find(dps, ".//sca:ContentType") if q is not None: dest.content_type = q.text q = wsd_common.xml_find(dps, ".//sca:InputSize") if q is not None: autod = wsd_common.xml_find(q, ".//sca:DocumentAutoDetect") if autod is not None: dest.size_autodetect = True if autod.text == 'true' or autod.text == '1' else False v1 = wsd_common.xml_find(q, ".//sca:InputMediaSize/sca:Width") v2 = wsd_common.xml_find(q, ".//sca:InputMediaSize/sca:Height") dest.input_size = (int(v1.text), int(v2.text)) q = wsd_common.xml_find(dps, ".//sca:Exposure") if q is not None: autod = wsd_common.xml_find(q, ".//sca:AutoExposure") if autod is not None: dest.auto_exposure = True if autod.text == 'true' or autod.text == '1' else False dest.contrast = int( wsd_common.xml_find(q, ".//sca:ExposureSettings/sca:Contrast").text) dest.brightness = int( wsd_common.xml_find(q, ".//sca:ExposureSettings/sca:Brightness").text) dest.sharpness = int( wsd_common.xml_find(q, ".//sca:ExposureSettings/sca:Sharpness").text) q = wsd_common.xml_find(dps, ".//sca:Scaling") if q is not None: v1 = wsd_common.xml_find(q, ".//sca:ScalingWidth") v2 = wsd_common.xml_find(q, ".//sca:ScalingHeight") dest.scaling = (int(v1.text), int(v2.text)) q = wsd_common.xml_find(dps, ".//sca:Rotation") if q is not None: dest.rotation = int(q.text) q = wsd_common.xml_find(dps, ".//sca:MediaSides") if q is not None: f = wsd_common.xml_find(q, ".//sca:MediaFront") dest.front = parse_media_side(f) f = wsd_common.xml_find(q, ".//sca:MediaBack") if f is not None: dest.back = parse_media_side(f) else: dest.back = copy.deepcopy(dest.front) return dest
def wsd_get(target_service: wsd_discovery__structures.TargetService): """ Query wsd target for information about model/device and hosted services. :param target_service: A wsd target :type target_service: wsd_discovery__structures.TargetService :return: A tuple containing a TargetInfo and a list of HostedService instances. """ fields = {"FROM": wsd_globals.urn, "TO": target_service.ep_ref_addr} x = wsd_common.submit_request(target_service.xaddrs, "ws-transfer__get.xml", fields) if x is False: return False meta = wsd_common.xml_find(x, ".//mex:Metadata") meta_model = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisModel']" ) meta_dev = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/ThisDevice']" ) meta_rel = wsd_common.xml_find( meta, ".//mex:MetadataSection[@Dialect=\ 'http://schemas.xmlsoap.org/ws/2006/02/devprof/Relationship']" ) tinfo = wsd_transfer__structures.TargetInfo() # WSD-Profiles section 5.1 (+ PNP-X) tinfo.manufacturer = wsd_common.xml_find(meta_model, ".//wsdp:Manufacturer").text q = wsd_common.xml_find(meta_model, ".//wsdp:ManufacturerUrl") if q is not None: tinfo.manufacturer_url = q.text tinfo.model_name = wsd_common.xml_find(meta_model, ".//wsdp:ModelName").text q = wsd_common.xml_find(meta_model, ".//wsdp:ModelNumber") if q is not None: tinfo.model_number = q.text q = wsd_common.xml_find(meta_model, ".//wsdp:ModelUrl") if q is not None: tinfo.model_url = q.text q = wsd_common.xml_find(meta_model, ".//wsdp:PresentationUrl") if q is not None: tinfo.presentation_url = q.text tinfo.device_cat = wsd_common.xml_find( meta_model, ".//pnpx:DeviceCategory").text.split() tinfo.friendly_name = wsd_common.xml_find(meta_dev, ".//wsdp:FriendlyName").text tinfo.fw_ver = wsd_common.xml_find(meta_dev, ".//wsdp:FirmwareVersion").text tinfo.serial_num = wsd_common.xml_find(meta_dev, ".//wsdp:SerialNumber").text hservices = [] # WSD-Profiles section 5.2 (+ PNP-X) wsd_common.xml_findall( meta_rel, ".//wsdp:Relationship[@Type='http://schemas.xmlsoap.org/ws/2006/02/devprof/host']" ) for r in meta_rel: # UNCLEAR how the host item should differ from the target endpoint, and how to manage multiple host items # TBD - need some real-case examples # host = xml_find(r, ".//wsdp:Host") # if host is not None: #"if omitted, implies the same endpoint reference of the targeted service" # xml_find(host, ".//wsdp:Types").text # xml_find(host, ".//wsdp:ServiceId").text # er = xml_find(host, ".//wsa:EndpointReference") # xml_find(er, ".//wsa:Address").text #Optional endpoint fields not implemented yet hosted = wsd_common.xml_findall(r, ".//wsdp:Hosted") for h in hosted: hs = wsd_transfer__structures.HostedService() hs.types = wsd_common.xml_find(h, ".//wsdp:Types").text.split() hs.service_id = wsd_common.xml_find(h, ".//wsdp:ServiceId").text q = wsd_common.xml_find(h, ".//pnpx:HardwareId") if q is not None: hs.hardware_id = q.text q = wsd_common.xml_find(h, ".//pnpx:CompatibleId") if q is not None: hs.compatible_id = q.text q = wsd_common.xml_find(h, ".//wsdp:ServiceAddress") if q is not None: hs.service_address = q.text er = wsd_common.xml_find(h, ".//wsa:EndpointReference") hs.ep_ref_addr = wsd_common.xml_find(er, ".//wsa:Address").text hservices.append(hs) # WSD-Profiles section 5.3 and 5.4 omitted return tinfo, hservices