1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# frozen_string_literal: true
# dbus/xml.rb - introspection parser, rexml/nokogiri abstraction
#
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
# Copyright (C) 2012 Geoff Youngs
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
# Our gemspec says rexml is needed and nokogiri is optional
# but in fact either will do
begin
require "nokogiri"
rescue LoadError
begin
require "rexml/document"
rescue LoadError
raise LoadError, "cannot load nokogiri OR rexml/document"
end
end
module DBus
# = D-Bus introspect XML parser class
#
# This class parses introspection XML of an object and constructs a tree
# of Node, Interface, Method, Signal instances.
class IntrospectXMLParser
class << self
attr_accessor :backend
end
# Creates a new parser for XML data in string _xml_.
# @param xml [String]
def initialize(xml)
@xml = xml
end
class AbstractXML
# @!method initialize(xml)
# @abstract
# @!method each(xpath)
# @abstract
# yields nodes which match xpath of type AbstractXML::Node
def self.have_nokogiri?
Object.const_defined?("Nokogiri")
end
class Node
def initialize(node)
@node = node
end
# required methods
# returns node attribute value
def [](key); end
# yields child nodes which match xpath of type AbstractXML::Node
def each(xpath); end
end
end
class NokogiriParser < AbstractXML
class NokogiriNode < AbstractXML::Node
def [](key)
@node[key]
end
def each(path, &block)
@node.search(path).each { |node| block.call NokogiriNode.new(node) }
end
end
def initialize(xml)
super()
@doc = Nokogiri.XML(xml)
end
def each(path, &block)
@doc.search("//#{path}").each { |node| block.call NokogiriNode.new(node) }
end
end
class REXMLParser < AbstractXML
class REXMLNode < AbstractXML::Node
def [](key)
@node.attributes[key]
end
def each(path, &block)
@node.elements.each(path) { |node| block.call REXMLNode.new(node) }
end
end
def initialize(xml)
super()
@doc = REXML::Document.new(xml)
end
def each(path, &block)
@doc.elements.each(path) { |node| block.call REXMLNode.new(node) }
end
end
@backend = if AbstractXML.have_nokogiri?
NokogiriParser
else
REXMLParser
end
# @return [Array(Array<Interface>,Array<String>)]
# a pair: [list of Interfaces, list of direct subnode names]
def parse
# Using a Hash instead of a list helps merge split-up interfaces,
# a quirk observed in ModemManager (I#41).
interfaces = Hash.new do |hash, missing_key|
hash[missing_key] = Interface.new(missing_key)
end
subnodes = []
t = Time.now
d = IntrospectXMLParser.backend.new(@xml)
d.each("node/node") do |e|
subnodes << e["name"]
end
d.each("node/interface") do |e|
i = interfaces[e["name"]]
e.each("method") do |me|
m = Method.new(me["name"])
parse_methsig(me, m)
i << m
end
e.each("signal") do |se|
s = Signal.new(se["name"])
parse_methsig(se, s)
i << s
end
e.each("property") do |pe|
p = Property.from_xml(pe)
i << p
end
end
d = Time.now - t
if d > 2
DBus.logger.debug "Some XML took more that two secs to parse. Optimize me!"
end
[interfaces.values, subnodes]
end
######################################################################
private
# Parses a method signature XML element *elem* and initialises
# method/signal *methsig*.
# @param elem [AbstractXML::Node]
def parse_methsig(elem, methsig)
elem.each("arg") do |ae|
name = ae["name"]
dir = ae["direction"]
sig = ae["type"]
case methsig
when DBus::Signal
# Direction can only be "out", ignore it
methsig.add_fparam(name, sig)
when DBus::Method
case dir
# This is a method, so dir defaults to "in"
when "in", nil
methsig.add_fparam(name, sig)
when "out"
methsig.add_return(name, sig)
end
else
raise NotImplementedError, dir
end
end
end
end
end
|