require 'voruby/adql/loader' # A set of classes designed to read and manipulate ADQL[http://www.ivoa.net/Documents/latest/ADQL.html]. module VORuby module ADQL # Acts as glue between xsi:types and their corresponding # domain objects in the * hierarchy. class ObjectBuilder def self.classes {:allSelectionItemType => AllSelectionItem, :columnReferenceType => ColumnReference, :tableType => ArchiveTable, :atomType => Atom, :stringType => StringType, :realType => RealType, :integerType => IntegerType, :binaryExprType => BinaryExpr, :unaryExprType => UnaryExpr, :closedExprType => ClosedExpr, :trigonometricFunctionType => TrigonometricFunction, :trigonometricFunctionNameType => TrigonometricFunctionName, :mathFunctionType => MathFunction, :mathFunctionNameType => MathFunctionName, :aggregateFunctionType => AggregateFunction, :aggregateFunctionNameType => AggregateFunctionName, :userDefinedFunctionType => UserDefinedFunction, :aliasSelectionItemType => AliasSelectionItem, :likePredType => LikePred, :notLikePredType => NotLikePred, :closedSearchType => ClosedSearch, :intersectionSearchType => IntersectionSearch, :unionSearchType => UnionSearch, :comparisonPredType => ComparisonPred, :betweenPredType => BetweenPred, :notBetweenPredType => NotBetweenPred, :includeTableType => IncludeTable, :dropTableType => DropTable, :xMatchType => XMatch, :xMatchTableAliasType => XMatchTableAlias, :comparisonType => Comparison, :regionSearchType => RegionSearch, :inverseSearchType => InverseSearch, :inclusionSetType => InclusionSet, :inclusiveSearchType => InclusiveSearch, :exclusiveSearchType => ExclusiveSearch, :subQuerySetType => SubQuerySet, :joinTableType => JoinTable, :constantListSetType => ConstantListSet, :orderType => Order, :circleType => Circle, :boxType => Box, :archiveTableType => ArchiveTable} end # Get the domain class associated with a particular xsi:type. def self.get_class_for(type) klass = self.classes()[type.to_sym] || self.classes()[type] raise "Unable to find type #{type}" if !klass return klass end end # The abstract base type for any of items to be selected in a query. class SelectionItem def self.from_xml(node) #type_s = node.elements['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') return ObjectBuilder.get_class_for(type_s).from_xml(node) end end # The base type for a scalar expression. class ScalarExpression < SelectionItem attr_reader :value def initialize(val) self.value = val end def is_scalar?(v) if v.is_a?(String) or v.is_a?(Integer) or v.is_a?(Float) return true else return false end end def value=(v) if self.is_scalar?(v) @value = v else raise "Scalar expression must contain scalar values" end end def to_s "{value=#{self.value}}" end def self.from_xml(node) #type_s = node.attributes['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') return ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Represents an expression inside a bracket. class ClosedExpr < ScalarExpression attr_reader :value def initialize(val) self.value = val end def value=(v) VOTables::VOTable::Misc::TypeCheck.new(v, ScalarExpression).check() @value = v end def self.from_xml(node) expr_node = REXML::XPath.first(node, 'Arg') expr = ScalarExpression.from_xml(expr_node) return ClosedExpr.new(expr) end end # Used for expressing operations like A+B. class BinaryOperator attr_reader :operator @@operators = ['+', '-', '*', '/'] def initialize(operator, op_list=nil) @op_list = op_list || @@operators self.operator = operator end def operator=(o) if @op_list.include?(o) @operator = o else raise "Binary operator is not valid. Use one of: " + @op_list.join(', ') end end def to_s "{operator=#{self.operator}}" end end # Represents a binary expression such as a+b. class BinaryExpr < ScalarExpression attr_reader :oper, :arg1, :arg2 def initialize(arg1, oper, arg2) super("#{arg1} #{oper} #{arg2}") self.arg1 = arg1 self.arg2 = arg2 self.oper = oper end def arg1=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg1 = ScalarExpression.new(a) else @arg1 = a end end def arg2=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg2 = ScalarExpression.new(a) else @arg2 = a end end def oper=(o) begin VOTables::VOTable::Misc::TypeCheck.new(o, BinaryOperator).check() rescue VOTables::VOTable::Misc::TypeException @oper = BinaryOperator.new(o) else @oper = o end end def to_s "{arg1=#{self.arg1},oper=#{self.oper},arg2=#{self.arg2}}" end def self.from_xml(node) oper = BinaryOperator.new(node.attributes['Oper']) arg1_node, arg2_node = node.elements.to_a('Arg') arg1 = ScalarExpression.from_xml(arg1_node) arg2 = ScalarExpression.from_xml(arg2_node) return BinaryExpr.new(arg1, oper, arg2) end end # Operators for expressing a single element operation. class UnaryOperator attr_reader :operator @@operators = ['+', '-'] def initialize(operator, op_list=nil) @op_list = op_list || @@operators self.operator = operator end def operator=(o) if @op_list.include?(o) @operator = o else raise "Unary operator is not valid. Use one of: " + @op_list.join(', ') end end def to_s "{operator=#{self.operator}}" end end # Represents an unary expression such as -(a.ra) class UnaryExpr < ScalarExpression attr_reader :arg, :oper def initialize(oper, arg) super("#{oper}#{arg}") self.arg = arg self.oper = oper end def arg=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg = ScalarExpression.new(a) else @arg = a end end def oper=(o) begin VOTables::VOTable::Misc::TypeCheck.new(o, UnaryOperator).check() rescue VOTables::VOTable::Misc::TypeException @oper = UnaryOperator.new(o) else @oper = o end end def to_s "{arg=#{self.oper},oper=#{self.arg}}" end def self.from_xml(node) oper = UnaryOperator.new(node.attributes['Oper']) arg_node = REXML::XPath.first(node, 'Arg') arg = ScalarExpression.from_xml(arg_node) return UnaryExpr.new(oper, arg) end end # Represents a column. class ColumnReference < ScalarExpression attr_accessor :table, :name, :xpath_name def initialize(table, name, xpath_name=nil) super("#{table}.#{name} #{xpath_name || ''}") self.table = table self.name = name self.xpath_name = xpath_name end def to_s self.value end def self.from_xml(el) table = el.attributes['Table'] name = el.attributes['Name'] xpath = el.attributes['xpathName'] cr = ColumnReference.new(table, name, xpath) end end # Encapsulates basic literals such as Strings, Integers and Real numbers. class Atom < ScalarExpression attr_reader :literal attr_accessor :unit def initialize(literal, unit=nil) super("#{literal}#{unit || ''}") self.literal = literal self.unit = unit end def literal=(l) begin VOTables::VOTable::Misc::TypeCheck.new(l, LiteralType).check() rescue VOTables::VOTable::Misc::TypeException if l.is_a?(Float) @literal = RealType.new(l) elsif l.is_a?(Integer) @literal = IntegerType.new(l) elsif l.is_a?(String) @literal = StringType.new(l) else raise "Literal is not a real, integer or string" end else @literal = l end end def to_s "{literal=#{self.literal},unit=#{self.unit}}" end def self.from_xml(node) literal_node = REXML::XPath.first(node, 'Literal') literal = LiteralType.from_xml(literal_node) return Atom.new(literal) end end # The base type for all literals. class LiteralType def to_s "{value=#{self.value}}" end def self.from_xml(node) #type_s = node.attributes['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') literal = ObjectBuilder.get_class_for(type_s).from_xml(node) return literal end end # The base type for all numbers. class NumberType < LiteralType def self.from_xml(node) #type_s = node.attributes['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') number = ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Represents a real number. class RealType < NumberType attr_reader :value def initialize(value) self.value = value end def value=(v) begin VOTables::VOTable::Misc::TypeCheck.new(v, Float).check() rescue VOTables::VOTable::Misc::TypeException if v.is_a?(Integer) @value = v.to_f else raise VOTables::VOTable::Misc::TypeException end else @value = v end end def self.from_xml(node) return RealType.new(node.attributes['Value'].to_f) end end # Represents an integer. class IntegerType < NumberType attr_reader :value def initialize(value) self.value = value end def value=(v) VOTables::VOTable::Misc::TypeCheck.new(v, Integer).check() @value = v end def self.from_xml(node) return IntegerType.new(node.attributes['Value'].to_i) end end # Represents a string literal. class StringType < LiteralType attr_reader :value def initialize(value) self.value = value end def value=(v) VOTables::VOTable::Misc::TypeCheck.new(v, String).check() @value = v end def self.from_xml(node) return StringType.new(node.attributes['Value']) end end # The base type for a function. class FunctionType < ScalarExpression def to_s "{name=#{self.name}}" end end # Option of selecting all or distinct elements in a query. class SelectionOption attr_reader :option def initialize(option) self.option = option end def option=(o) begin VOTables::VOTable::Misc::TypeCheck.new(o, AllOrDistinct).check() rescue VOTables::VOTable::Misc::TypeException @option = AllOrDistinct.new(o) else @option = o end end def content self.option.option end def to_s "{option=#{self.option}}" end def self.from_xml(node) option = AllOrDistinct.new(node.attributes['Option']) return SelectionOption.new(option) end end # Enumeration for All and Distinct options. class AllOrDistinct attr_reader :option, :option_list @@options = ['ALL', 'DISTINCT'] def initialize(option, option_list=nil) @option_list = option_list || @@options self.option = option end def option=(o) if @option_list.include?(o) @option = o else raise "Option is not valid. Use one of: " + @option_list.join(', ') end end def to_s "{option=#{self.option}}" end end # Represents a trigonometric function. class TrigonometricFunction < FunctionType attr_reader :name, :arg, :allow def initialize(name, arg, allow=nil) self.name = name self.arg = arg self.allow = allow end def name=(n) begin VOTables::VOTable::Misc::TypeCheck.new(n, TrigonometricFunctionName).check() rescue VOTables::VOTable::Misc::TypeException @name = TrigonometricFunctionName.new(n) else @name = n end end def arg=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionItem).check() @arg = a end def allow=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionOption).check() @allow = a end def to_s "{name=#{self.name},allow=#{self.allow},arg=#{self.arg}}" end def self.from_xml(node) name = TrigonometricFunctionName.from_xml(node) arg_node = REXML::XPath.first(node, 'Arg') arg = Arg.from_xml(arg_node) allow_node = REXML::XPath.first(node, 'Allow') allow = nil if allow_node allow = Allow.from_xml(allow_node) end return TrigonometricFunction.new(name, arg, allow) end end # Enumeration of allowed trigonometric functions. class TrigonometricFunctionName attr_reader :value, :values_list @@values = ['SIN', 'COS', 'TAN', 'COT', 'ASIN', 'ACOS', 'ATAN', 'ATAN2'] def initialize(value, values_list=nil) @values_list = values_list || @@values self.value = value end def value=(v) if @values_list.include?(v) @value = v else raise "Value is not valid. Use one of: " + @values_list.join(', ') end end def to_s "{value=#{self.value}}" end def self.from_xml(node) return TrigonometricFunctionName.new(node.attributes['Name']) end end # Represents a math function. class MathFunction < FunctionType attr_reader :name, :arg, :allow def initialize(name, arg, allow=nil) self.name = name self.arg = arg self.allow = allow end def name=(n) begin VOTables::VOTable::Misc::TypeCheck.new(n, MathFunctionName).check() rescue VOTables::VOTable::Misc::TypeException @name = MathFunctionName.new(n) else @name = n end end def arg=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionItem).check() @arg = a end def allow=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionOption).check() @allow = a end def self.from_xml(node) name = MathFunctionName.from_xml(node) arg_node = REXML::XPath.first(node, 'Arg') arg = Arg.from_xml(arg_node) allow_node = REXML::XPath.first(node, 'Allow') allow = nil if allow_node allow = Allow.from_xml(allow_node) end return MathFunction.new(name, arg, allow) end def to_s "{name=#{self.name},arg=#{self.arg},allow=#{self.allow}}" end end # Enumeration of allowed math functions. class MathFunctionName attr_reader :value, :values_list @@values = ['ABS', 'CEILING', 'DEGREES', 'EXP', 'FLOOR', 'LOG', 'PI', 'POWER', 'RADIANS', 'SQRT', 'SQUARE', 'LOG10', 'RAND', 'ROUND', 'TRUNCATE'] def initialize(value, values_list=nil) @values_list = values_list || @@values self.value = value end def value=(v) if @values_list.include?(v) @value = v else raise "Value is not valid. Use one of: " + @values_list.join(', ') end end def to_s "{value=#{self.value}}" end def self.from_xml(node) return MathFunctionName.new(node.attributes['Name']) end end # Represents an aggregate function. class AggregateFunction < FunctionType attr_reader :name, :arg, :allow def initialize(name, arg, allow=nil) self.name = name self.arg = arg self.allow = allow end def name=(n) begin VOTables::VOTable::Misc::TypeCheck.new(n, AggregateFunctionName).check() rescue VOTables::VOTable::Misc::TypeException @name = AggregateFunctionName.new(n) else @name = n end end def arg=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionItem).check() @arg = a end def allow=(a) VOTables::VOTable::Misc::TypeCheck.new(a, SelectionOption).check() @allow = a end def to_s "{name=#{self.name},arg=#{self.arg},allow=#{self.allow}}" end def self.from_xml(node) name = AggregateFunctionName.from_xml(node) arg_node = REXML::XPath.first(node, 'Arg') arg = Arg.from_xml(arg_node) allow_node = REXML::XPath.first(node, 'Allow') allow = nil if allow_node allow = Allow.from_xml(allow_node) end return AggregateFunction.new(name, arg, allow) end end # Enumeration of allowed aggregate functions. class AggregateFunctionName attr_reader :value, :values_list @@values = ['AVG', 'MIN', 'MAX', 'SUM', 'COUNT'] def initialize(value, values_list=nil) @values_list = values_list || @@values self.value = value end def value=(v) if @values_list.include?(v) @value = v else raise "Value is not valid. Use one of: " + @values_list.join(', ') end end def to_s "{value=#{self.value}}" end def self.from_xml(node) return AggregateFunctionName.new(node.attributes['Name']) end end # Used to select an expression as a new alias column. class AliasSelectionItem < SelectionItem attr_reader :expression attr_accessor :as def initialize(expression, as=nil) self.expression = expression self.as = as end def expression=(e) begin VOTables::VOTable::Misc::TypeCheck.new(e, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @expression = ScalarExpression.new(e) else @expression = e end end def to_s "{expression=#{self.expression},as=#{self.as}}" end def self.from_xml(node) as = node.attributes['As'] expr_node = REXML::XPath.first(node, 'Expression') expr = ScalarExpression.from_xml(expr_node) return AliasSelectionItem.new(expr, as) end end # Represent all columns as in Select * query. class AllSelectionItem < SelectionItem @@value = '*' def initialize;end def self.value return @@value end def to_s "{expression=#{self.value}}" end def self.from_xml(node) return AllSelectionItem.new() end end # The comparison operators such as Less-than or More-than, etc. class Comparison attr_reader :value, :values_list @@values = ['=', '<>', '>', '>=', '<', '<='] def initialize(value, values_list=nil) @values_list = values_list || @@values self.value = value end def value=(v) if @values_list.include?(v) @value = v else raise "Value is not valid. Use one of: " + @values_list.join(', ') end end def to_s "{value=#{self.value}}" end def self.from_xml(node) return Comparison.new(node.attributes['Comparison']) end end # The base type for all tables used in the From clause of the query. class FromTable def self.from_xml(node) type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') table = ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Same as a TableType with an additional archive name. class ArchiveTable < FromTable attr_accessor :archive, :name, :alias_name def initialize(archive, name, alias_name=nil) self.archive = archive self.name = name self.alias_name = alias_name end def to_s "{archive=#{self.archive},name=#{self.name},alias=#{self.alias_name}}" end def self.from_xml(node) archive = node.attributes['Archive'] or raise "No ArchiveTable attribute 'Archive'" #name = CGI::escapeHTML(node.attributes['Name']) or raise "No ArchiveTable attribute 'Name'" name = node.attributes['Name'] or raise "No ArchiveTable attribute 'Name'" alias_name = node.attributes['Alias'] at = ArchiveTable.new(archive, name, alias_name) end end # Represents a table with its name and its alias name. class Table < FromTable attr_accessor :name, :alias_name, :xpath_name def initialize(name, alias_name=nil, xpath_name=nil) self.name = name self.alias_name = alias_name self.xpath_name = xpath_name end def to_s "{name=#{self.name},alias=#{self.alias_name},xpath=#{self.xpath_name}}" end def self.from_xml(node) type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') table = ObjectBuilder.get_class_for(type_s).from_xml(node) end end # The base type for all table inclusion or drop types used in a cross match expression. class XMatchTableAlias def to_s "{name=#{self.name}}" end def self.from_xml(node) #type_s = node.attributes['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') xtable = ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Used for adding a table for the Xmatch operation. class IncludeTable < XMatchTableAlias attr_accessor :name def initialize(name) self.name = name end def self.from_xml(node) name = node.attributes['Name'] return IncludeTable.new(name) end end # Used for avoiding a table in Xmatch. class DropTable < XMatchTableAlias attr_accessor :name def initialize(name) self.name = name end def self.from_xml(node) name = node.attributes['Name'] return DropTable.new(name) end end # The base type for searches in Where and Having clauses of the query. class Search def self.from_xml(node) type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') search = ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Represents expressions like a AND b. class IntersectionSearch < Search attr_reader :cond1, :cond2 def initialize(cond1, cond2) self.cond1 = cond1 self.cond2 = cond2 end def cond1=(cond) VOTables::VOTable::Misc::TypeCheck.new(cond, Search).check() @cond1 = cond end def cond2=(cond) VOTables::VOTable::Misc::TypeCheck.new(cond, Search).check() @cond2 = cond end def to_s "{cond1=#{self.cond1},cond2=#{self.cond2}}" end def self.from_xml(node) cond1_node, cond2_node = node.elements.to_a('Condition') cond1 = Search.from_xml(cond1_node) cond2 = Search.from_xml(cond2_node) return IntersectionSearch.new(cond1, cond2) end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) condition = self.cond1.find_condition(type, attributes) if condition == nil condition = self.cond2.find_condition(type, attributes) end return condition end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) cond1 = self.cond1.remove_condition(type, attributes) if cond1 == -1 return self.cond2 elsif cond1 == 1 cond2 = self.cond2.remove_condition(type, attributes) if cond2 == -1 return self.cond1 elsif cond2 == 1 return 1 end else self.cond1 = cond1 return self end end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) cond1 = self.cond1.modify_condition(type, attributes_old, attributes_new) if cond1 != nil self.cond1 = cond1 else cond2 = self.cond2.modify_condition(type, attributes_old, attributes_new) if cond2 != nil self.cond2 = cond2 else return nil end end return self end # This method replaces a condition for a new condition. -1 means that # the condition must be replaced for the new condition. 1 means that # the process must continue. def replace_condition(type, attributes, new_condition) cond1 = self.cond1.replace_condition(type, attributes, new_condition) if cond1 == -1 self.cond1 = new_condition return self elsif cond1 == 1 cond2 = self.cond2.replace_condition(type, attributes, new_condition) if cond2 == -1 self.cond2 = new_condition return self elsif cond2 == 1 return 1 end else return self end end end # Represents expressions like A Or B. class UnionSearch < Search attr_reader :cond1, :cond2 def initialize(cond1, cond2) self.cond1 = cond1 self.cond2 = cond2 end def cond1=(cond) VOTables::VOTable::Misc::TypeCheck.new(cond, Search).check() @cond1 = cond end def cond2=(cond) VOTables::VOTable::Misc::TypeCheck.new(cond, Search).check() @cond2 = cond end def to_s "{cond1=#{self.cond1},cond2=#{self.cond2}}" end def self.from_xml(node) cond1_node, cond2_node = node.elements.to_a('Condition') cond1 = Search.from_xml(cond1_node) cond2 = Search.from_xml(cond2_node) return UnionSearch.new(cond1, cond2) end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) condition = self.cond1.find_condition(type, attributes) if condition == nil condition = self.cond2.find_condition(type, attributes) end return condition end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) cond1 = self.cond1.remove_condition(type, attributes) if cond1 == -1 return self.cond2 elsif cond1 == 1 cond2 = self.cond2.remove_condition(type, attributes) if cond2 == -1 return self.cond1 elsif cond2 == 1 return 1 end else self.cond1 = cond1 return self end end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) cond1 = self.cond1.modify_condition(type, attributes_old, attributes_new) if cond1 != nil self.cond1 = cond1 else cond2 = self.cond2.modify_condition(type, attributes_old, attributes_new) if cond2 != nil self.cond2 = cond2 else return nil end end return self end # This method replaces a condition for a new condition. -1 means that # the condition must be replaced for the new condition. 1 means that # the process must continue def replace_condition(type, attributes, new_condition) cond1 = self.cond1.replace_condition(type, attributes, new_condition) if cond1 == -1 self.cond1 = new_condition return self elsif cond1 == 1 cond2 = self.cond2.replace_condition(type, attributes, new_condition) if cond2 == -1 self.cond2 = new_condition return self elsif cond2 == 1 return 1 end else return self end end end # A cross match expression. class XMatch < Search attr_reader :tables, :nature, :sigma def initialize(tables, nature, sigma) self.tables = tables self.nature = nature self.sigma = sigma end def tables=(ts) raise "Specify at least two tables" if !ts or ts.length < 2 ts.each do |t| VOTables::VOTable::Misc::TypeCheck.new(t, XMatchTableAlias) end @tables = ts end def nature=(n) begin VOTables::VOTable::Misc::TypeCheck.new(n, Comparison).check() rescue VOTables::VOTable::Misc::TypeException @nature = Comparison.new(n) else @nature = n end end def sigma=(s) begin VOTables::VOTable::Misc::TypeCheck.new(s, NumberType).check() rescue VOTables::VOTable::Misc::TypeException if s.is_a?(Float) @sigma = RealType.new(s) elsif s.is_a?(Integer) @sigma = IntegerType.new(s) else raise "Sigma must be a float or integer" end else @sigma = s end end def to_s tables = self.tables.collect{|x| x.to_s}.join('|') "{tables=#{tables},nature=#{self.nature},sigma=#{self.sigma}}" end def self.from_xml(node) tables = [] node.elements.each('Table') do |tbl_node| table = XMatchTableAlias.from_xml(tbl_node) tables.push(table) end nature_node = REXML::XPath.first(node, 'Nature') nature = Nature.from_xml(nature_node) sigma_node = REXML::XPath.first(node, 'Sigma') sigma = Sigma.from_xml(sigma_node) return XMatch.new(tables, nature, sigma) end end # The Like expression of a query. class LikePred < Search attr_reader :arg, :pattern def initialize(arg, pattern) self.arg = arg self.pattern = pattern end def arg=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg = ScalarExpression.new(a) else @arg = a end end def pattern=(p) begin VOTables::VOTable::Misc::TypeCheck.new(p, Atom).check() rescue VOTables::VOTable::Misc::TypeException @pattern = Atom.new(p) else @pattern = p end end def to_s "{arg=#{self.arg},pattern=#{self.pattern}}" end def self.from_xml(node) arg_node = REXML::XPath.first(node, 'Arg') arg = Arg.from_xml(arg_node) pattern_node = REXML::XPath.first(node, 'Pattern') pattern = Pattern.from_xml(pattern_node) return LikePred.new(arg, pattern) end def self.create_new_object(attributes) arg = ColumnReference.new(attributes['table'], attributes['name'], nil) pattern = StringType.new(attributes['pattern']) return LikePred.new(arg, pattern) end def match_attributtes(attributes) return true if self.arg.table == attributes['table'] and self.arg.name == attributes['name'] and self.pattern.literal.value == attributes['pattern'] return false end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return self if self.match_attributtes(attributes) return nil end return nil end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() if self.match_attributtes(attributes_old) return LikePred.create_new_object(attributes_new) else return nil end end return nil end # This method replaces a condition given its attributes. # Returns -1 if the object must be replaced, or returns 1 if # isn't the object that must be replaced, so the process must continue def replace_condition(type, attributes, new_condition) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end end # The Not Like expression of a query. class NotLikePred < LikePred def self.from_xml(node) arg_node = REXML::XPath.first(node, 'Arg') arg = Arg.from_xml(arg_node) pattern_node = REXML::XPath.first(node, 'Pattern') pattern = Pattern.from_xml(pattern_node) return NotLikePred.new(arg, pattern) end end # Represents SQL NOT IN expression. class ExclusiveSearch < Search attr_reader :expression, :set def initialize(expression, set) self.expression = expression self.set = set end def expression=(e) begin VOTables::VOTable::Misc::TypeCheck.new(e, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @expression = ScalarExpression.new(e) else @expression = e end end def set=(s) VOTables::VOTable::Misc::TypeCheck.new(s, InclusionSet).check() @set = s end def to_s "{expression=#{self.expression},set=#{self.set}}" end def self.from_xml(node) expr_node = REXML::XPath.first(node, 'Expression') expr = Expression.from_xml(expr_node) set_node = REXML::XPath.first(node, 'Set') set = Set.from_xml(set_node) return ExclusiveSearch.new(expr, set) end end # The base type for selection set in a SQL IN expression. class InclusionSet def self.from_xml(node) #type_s = node.attributes['xsi:type'] type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') return ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Represents the subquery in a SQL IN expression. class SubQuerySet < InclusionSet attr_reader :selection def initialize(selection) self.selection = selection end def selection=(s) VOTables::VOTable::Misc::TypeCheck.new(s, Select).check() @selection = s end def to_s "{selection=#{self.selection}}" end def self.from_xml(node) select_node = REXML::XPath.first(node, 'Select') select = Select.from_xml(select_node) return SubQuerySet.new(select) end end # Represents expressions like (A). class ClosedSearch < Search attr_reader :condition def initialize(condition) self.condition = condition end def condition=(c) VOTables::VOTable::Misc::TypeCheck.new(c, Search).check() @condition = c end def to_s "{condition=#{self.condition}}" end def self.from_xml(node) cond_node = REXML::XPath.first(node, 'Condition') cond = Condition.from_xml(cond_node) return ClosedSearch.new(cond) end end # Represents the Comparison of two expressions. class ComparisonPred < Search attr_reader :arg1, :arg2, :comparison def initialize(arg1, comparison, arg2) self.arg1 = arg1 self.comparison = comparison self.arg2 = arg2 end def arg1=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg1 = ScalarExpression.new(a) else @arg1 = a end end def arg2=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg2 = ScalarExpression.new(a) else @arg2 = a end end def comparison=(c) begin VOTables::VOTable::Misc::TypeCheck.new(c, Comparison).check() rescue VOTables::VOTable::Misc::TypeException @comparison = Comparison.new(c) else @comparison = c end end def to_s "{arg1=#{self.arg1},comparison=#{self.comparison},arg2=#{self.arg2}}" end def self.from_xml(node) comparison_s = node.attributes['Comparison'] arg1_node, arg2_node = node.elements.to_a('Arg') arg1 = Arg.from_xml(arg1_node) arg2 = Arg.from_xml(arg2_node) return ComparisonPred.new(arg1, comparison_s, arg2) end def self.create_new_object(attributes) arg1 = ColumnReference.new(attributes['table'], attributes['name'], nil) arg2 = nil if attributes['value'].is_a?(Float) arg2 = Atom.new(RealType.new(attributes['value'])) elsif attributes['value'].is_a?(Integer) arg2 = Atom.new(IntegerType.new(attributes['value'])) elsif attributes['value'].is_a?(String) arg2 = Atom.new(StringType.new(attributes['value'])) else raise "value is not a real, integer or string" end return ComparisonPred.new(arg1, attributes['comparison'], arg2) end def match_attributtes(attributes) return true if self.arg1.table == attributes['table'] and self.arg1.name == attributes['name'] and self.comparison.value == attributes['comparison'] and self.arg2.literal.value == attributes['value'] return false end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return self if self.match_attributtes(attributes) return nil end return nil end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() if self.match_attributtes(attributes_old) return ComparisonPred.create_new_object(attributes_new) else return nil end end return nil end # This method replaces a condition given its attributes. # Returns -1 if the object must be replaced, or returns 1 if # isn't the object that must be replaced, so the process must continue def replace_condition(type, attributes, new_condition) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end end # Represents the Between expression of a query. class BetweenPred < Search attr_reader :arg1, :arg2, :arg3 def initialize(arg1, arg2, arg3) self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3 end def arg1=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg1 = ScalarExpression.new(a) else @arg1 = a end end def arg2=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg2 = ScalarExpression.new(a) else @arg2 = a end end def arg3=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, ScalarExpression).check() rescue VOTables::VOTable::Misc::TypeException @arg3 = ScalarExpression.new(a) else @arg3 = a end end def to_s "{arg1=#{self.arg1},arg2=#{self.arg2},arg3=#{self.arg3}}" end def self.from_xml(node) arg1_node, arg2_node, arg3_node = node.elements.to_a('Arg') arg1 = Arg.from_xml(arg1_node) arg2 = Arg.from_xml(arg2_node) arg3 = Arg.from_xml(arg3_node) return BetweenPred.new(arg1, arg2, arg3) end def self.create_new_object(attributes) arg1 = ColumnReference.new(attributes['table'], attributes['name'], nil) arg2 = nil arg3 = nil if attributes['value_min'].is_a?(Float) and attributes['value_max'].is_a?(Float) arg2 = Atom.new(RealType.new(attributes['value_min'])) arg3 = Atom.new(RealType.new(attributes['value_max'])) elsif attributes['value_min'].is_a?(Integer) and attributes['value_max'].is_a?(Integer) arg2 = Atom.new(IntegerType.new(attributes['value_min'])) arg3 = Atom.new(IntegerType.new(attributes['value_max'])) else raise "value is not a real or integer" end return BetweenPred.new(arg1, arg2, arg3) end def match_attributtes(attributes) return true if self.arg1.table == attributes['table'] and self.arg1.name == attributes['name'] and self.arg2.literal.value == attributes['value_min'] and self.arg3.literal.value == attributes['value_max'] return false end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return self if self.match_attributtes(attributes) return nil end return nil end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() if self.match_attributtes(attributes_old) return BetweenPred.create_new_object(attributes_new) else return nil end end return nil end # This method replaces a condition given its attributes. # Returns -1 if the object must be replaced, or returns 1 if # isn't the object that must be replaced, so the process must continue def replace_condition(type, attributes, new_condition) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end end # Represents expressions like Not A. class NotBetweenPred < BetweenPred def self.from_xml(node) arg1_node, arg2_node, arg3_node = node.elements.to_a('Arg') arg1 = Arg.from_xml(arg1_node) arg2 = Arg.from_xml(arg2_node) arg3 = Arg.from_xml(arg3_node) return NotBetweenPred.new(arg1, arg2, arg3) end end # Abstract Shape type. Shape definitions; Allsky, Circle, Polygon, # Box, and Sector are derived from Shape; Ellipse is derived from # Circle; Polygon includes also Vertex and SmallCircle class Shape def self.from_xml(node) type_s = node.find_attribute('type', 'http://www.w3.org/2001/XMLSchema-instance') type_s = type_s.slice(type_s.index(':')+1, type_s.length) return ObjectBuilder.get_class_for(type_s).from_xml(node) end end # Circle shape. The circle is defined by a center and a radius class Circle < Shape attr_reader :ra, :dec, :radius, :system, :shape def initialize(ra, dec, radius, system='J2000') self.ra = ra self.dec = dec self.radius = radius self.system = system self.shape = 'CIRCLE' end def ra=(ra) begin VOTables::VOTable::Misc::TypeCheck.new(ra, RealType).check() rescue VOTables::VOTable::Misc::TypeException @ra = RealType.new(ra) else @ra = ra end end def dec=(dec) begin VOTables::VOTable::Misc::TypeCheck.new(dec, RealType).check() rescue VOTables::VOTable::Misc::TypeException @dec = RealType.new(dec) else @dec = dec end end def radius=(radius) begin VOTables::VOTable::Misc::TypeCheck.new(radius, RealType).check() rescue VOTables::VOTable::Misc::TypeException @radius = RealType.new(radius) else @radius = radius end end def system=(sys) begin VOTables::VOTable::Misc::TypeCheck.new(sys, StringType).check() rescue VOTables::VOTable::Misc::TypeException @system = StringType.new(sys) else @system = sys end end def shape=(sh) begin VOTables::VOTable::Misc::TypeCheck.new(sh, StringType).check() rescue VOTables::VOTable::Misc::TypeException @shape = StringType.new(sh) else @shape = sh end end def to_s "{shape=#{self.shape},system=#{self.system},ra=#{self.ra},dec=#{self.dec},radius=#{self.radius}}" end def self.from_xml(node) unit = node.attributes['unit'] || 'deg' center = REXML::XPath.first(node, 'reg:Center', {'reg' => 'http://www.ivoa.net/xml/STC/STCregion/v1.10'}).text ra_s, dec_s = center.split(/\s+/) ra = RealType.new(ra_s.to_f) dec = RealType.new(dec_s.to_f) radius_s = REXML::XPath.first(node, 'reg:Radius', {'reg' => 'http://www.ivoa.net/xml/STC/STCregion/v1.10'}).text radius = RealType.new(radius_s.to_f) return Circle.new(ra, dec, radius) end def self.create_new_object(attributes) ra = RealType.new(attributes['ra']) dec = RealType.new(attributes['dec']) radius = RealType.new(attributes['radius']) return Circle.new(ra, dec, radius) end def match_attributtes(attributes) return true if self.ra.value == attributes['ra'] and self.dec.value == attributes['dec'] and self.radius.value == attributes['radius'] return false end end # Box shape. The box is defined by a center and a size class Box < Shape attr_reader :ra, :dec, :dra, :ddec, :system, :shape def initialize(ra, dec, dra, ddec, system='J2000') self.ra = ra self.dec = dec self.dra = dra#deltha RA self.ddec = ddec#deltha DEC self.system = system self.shape = 'BOX' end def ra=(ra) begin VOTables::VOTable::Misc::TypeCheck.new(ra, RealType).check() rescue VOTables::VOTable::Misc::TypeException @ra = RealType.new(ra) else @ra = ra end end def dec=(dec) begin VOTables::VOTable::Misc::TypeCheck.new(dec, RealType).check() rescue VOTables::VOTable::Misc::TypeException @dec = RealType.new(dec) else @dec = dec end end def dra=(dra) begin VOTables::VOTable::Misc::TypeCheck.new(dra, RealType).check() rescue VOTables::VOTable::Misc::TypeException @dra = RealType.new(dra) else @dra = dra end end def ddec=(ddec) begin VOTables::VOTable::Misc::TypeCheck.new(ddec, RealType).check() rescue VOTables::VOTable::Misc::TypeException @ddec = RealType.new(ddec) else @ddec = ddec end end def system=(sys) begin VOTables::VOTable::Misc::TypeCheck.new(sys, StringType).check() rescue VOTables::VOTable::Misc::TypeException @system = StringType.new(sys) else @system = sys end end def shape=(sh) begin VOTables::VOTable::Misc::TypeCheck.new(sh, StringType).check() rescue VOTables::VOTable::Misc::TypeException @shape = StringType.new(sh) else @shape = sh end end def to_s "{shape=#{self.shape},system=#{self.system},ra=#{self.ra}," + "dec=#{self.dec},dra=#{self.dra},ddec=#{self.ddec}}" end def self.from_xml(node) unit = node.attributes['unit'] || 'deg' center = REXML::XPath.first(node, 'reg:Center').text ra_s, dec_s = center.split(/\s+/) ra = RealType.new(ra_s.to_f) dec = RealType.new(dec_s.to_f) size = REXML::XPath.first(node, 'reg:Size').text dra_s, ddec_s = size.split(/\s+/) dra = RealType.new(dra_s.to_f) ddec = RealType.new(ddec_s.to_f) return Box.new(ra, dec, dra, ddec) end def self.create_new_object(attributes) ra = RealType.new(attributes['ra']) dec = RealType.new(attributes['dec']) dra = RealType.new(attributes['dra']) ddec = RealType.new(attributes['ddec']) return Box.new(ra, dec, dra, ddec) end def match_attributtes(attributes) return true if self.ra.value == attributes['ra'] and self.dec.value == attributes['dec'] and self.dra.value == attributes['dra'] and self.ddec.value == attributes['ddec'] return false end end # Represents the Regions such as circle in Where clause. class RegionSearch < Search attr_reader :shape, :intersection def initialize(shape, intersection='overlaps') self.shape = shape self.intersection = intersection end def shape=(s) VOTables::VOTable::Misc::TypeCheck.new(s, Shape).check() @shape = s end def intersection=(i) begin VOTables::VOTable::Misc::TypeCheck.new(i, StringType).check() rescue VOTables::VOTable::Misc::TypeException @intersection = StringType.new(i) else @intersection = i end end def to_s "{region={intersection=#{self.intersection},shape=#{self.shape}}}" end def self.from_xml(node) inter = node.attributes['intersection'].to_s region_node = REXML::XPath.first(node, 'Region') sh = Shape.from_xml(region_node) return RegionSearch.new(sh, inter) end def self.create_new_object(attributes) sh = ObjectBuilder.get_class_for(attributes['shape_type']).create_new_object(attributes) return RegionSearch.new(sh, attributes['intersection'].to_s) end def match_attributtes(attributes) return self.shape.match_attributtes(attributes) end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return self if self.match_attributtes(attributes) return nil end return nil end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found, so that the process must continue. def remove_condition(type, attributes) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end # This method modifies a condition given its old attributes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() if self.match_attributtes(attributes_old) return RegionSearch.create_new_object(attributes_new) else return nil end end return nil end # This method replaces a condition given its attributes. # Returns -1 if the object must be replaced, or returns 1 if # isn't the object that must be replaced, so the process must continue def replace_condition(type, attributes, new_condition) if ObjectBuilder.get_class_for(type).to_s() == self.class.to_s() return -1 if self.match_attributtes(attributes) return 1 end return 1 end end # Represents expressions like Not A. class InverseSearch < Search attr_reader :condition def initialize(condition) self.condition = condition end def condition=(c) VOTables::VOTable::Misc::TypeCheck.new(c, Search).check() @condition = c end def to_s "{condition=#{self.condition}}" end def self.from_xml(node) cond_node = REXML::XPath.first(node, 'Condition') cond = Condition.from_xml(cond_node) return InverseSearch.new(cond) end end # Represents the Having expression part of a query. class Having attr_reader :condition def initialize(condition) self.condition = condition end def condition=(c) VOTables::VOTable::Misc::TypeCheck.new(c, Search).check() @condition = c end def to_s "{condition=#{self.condition}}" end def self.from_xml(node) cond_node = REXML::XPath.first(node, 'Condition') cond = Condition.from_xml(cond_node) return Having.new(cond) end end # Represents the Group By expression part of a query. class GroupBy attr_reader :columns def initialize(columns) self.columns = columns end def columns=(c) raise "Provide at least one condition" if c.size < 1 @columns = [] c.each do |col| VOTables::VOTable::Misc::TypeCheck.new(col, ColumnReference).check() @columns.push(col) end end def to_s cols = self.columns.collect{|x| x.to_s}.join('|') "{columns=#{cols}}" end def self.from_xml(node) columns = [] node.elements.each('Column') do |col_node| col = Column.from_xml(col_node) columns.push(col) end return GroupBy.new(columns) end end # Represents the Where part of the query. class Where attr_reader :condition def initialize(condition) self.condition = condition end def condition=(c) VOTables::VOTable::Misc::TypeCheck.new(c, Search).check() @condition = c end def to_s "{condition=#{self.condition}}" end def self.from_xml(node) cond_node = REXML::XPath.first(node, 'Condition') cond = Condition.from_xml(cond_node) return Where.new(cond) end # This method finds a condition given its attributes and it returns it def find_condition(type, attributes) return self.condition.find_condition(type, attributes) end # This method removes a condition. -1 means that the condition must # be removed. 1 means that the condition given its attributes wasn't # found. The other case is that the condition was changed in another # place, for example could have been removed in the intersectionSearchType # called. def remove_condition(type, attributes) condition = self.condition.remove_condition(type, attributes) if condition == -1 self.condition = nil elsif condition != 1 self.condition = condition end end # This method modify a condition given its old attributtes, # replacing the old attributes with the new attributes. def modify_condition(type, attributes_old, attributes_new) self.condition = self.condition.modify_condition(type, attributes_old, attributes_new) end # This method replaces a condition for a new condition. -1 means that # the condition must be replaced. 1 means that the old condition in # this case wasn't found so that the old condition won't be replaced. # The other case is that the condition was changed in another place, # for example could have been replaced in the intersectionSearchType # called. def replace_condition(type, attributes, new_condition) condition = self.condition.replace_condition(type, attributes, new_condition) if condition == -1 self.condition = new_condition elsif condition != 1 self.condition = condition end end end # Represents the From part of the query. class From attr_reader :tables def initialize(tables) self.tables = tables end def tables=(ts) raise "Specify at least one table" if ts.size < 1 @tables = [] ts.each do |t| VOTables::VOTable::Misc::TypeCheck.new(t, FromTable).check() @tables.push(t) end end def to_s tables = self.tables.collect{|x| x.to_s}.join('|') "{tables=#{tables}}" end def self.from_xml(node) table_list = [] node.elements.each("Table") do |tbl_node| table = Table.from_xml(tbl_node) table_list.push(table) end return From.new(table_list) end end # List of items to be selected in the Query. class SelectionList attr_reader :items def initialize(items) self.items = items end def items=(is) raise "Specify at least one item" if is.size < 1 @items = [] is.each do |i| VOTables::VOTable::Misc::TypeCheck.new(i, SelectionItem).check() @items.push(i) end end def to_s items = self.items.collect{|x| x.to_s}.join('|') return "{items=#{items}}" end def self.from_xml(node) item_list = [] node.elements.each('Item') do |item_node| item_list.push(Item.from_xml(item_node)) end return SelectionList.new(item_list) end def add(attributes) new_item = nil if attributes['name'] == AllSelectionItem.value new_item = AllSelectionItem.new() else new_item = ColumnReference.new(attributes['table'], attributes['name'], attributes['xpath_name']) end self.items.push(new_item) if new_item and find(attributes) == nil end def remove(attributes) remove_item = find(attributes) self.items.delete(remove_item) if remove_item != nil end def find(attributes) self.items().each do |item| if !item.is_a?(AllSelectionItem) return item if item.name() == attributes['name'] and item.table() == attributes['table'] elsif item.is_a?(AllSelectionItem) return item if AllSelectionItem.value == attributes['name'] end end return nil end def is_empty self.items().empty? end end # Represents the TOP part of a query. class SelectionLimit attr_reader :top def initialize(top) self.top = top end def top=(t) raise "Provide an integer > 0" if t < 1 or !t.is_a?(Integer) @top = t end def to_s "{top=#{self.top}}" end def self.from_xml(node) top = node.attributes['Top'].to_i return SelectionLimit.new(top) end end # Represents the SQL INTO expression. class Into attr_accessor :table_name def initialize(name) self.table_name = name end def to_s "{table_name=#{self.table_name}}" end def self.from_xml(node) table_name_node = REXML::XPath.first(node, 'TableName') table_name = TableName.from_xml(table_name_node) return Into.new(table_name) end end # Ascending or Descending order of an Order by term. class OrderDirection attr_reader :value @@options = ['ASC', 'DESC'] def initialize(value, options_list=nil) @options_list = options_list || @@options self.value = value end def value=(v) if @options_list.include?(v) @value = v else raise "Value is not valid. Use one of: " + @options_list.join(', ') end end def to_s "{value=#{self.value}}" end end # Option for setting the direction for Order By. class OrderOption attr_reader :direction def initialize(direction) self.direction = direction end def direction=(d) begin VOTables::VOTable::Misc::TypeCheck.new(d, OrderDirection).check() rescue VOTables::VOTable::Misc::TypeException @direction = OrderDirection.new(d) else @direction = d end end def to_s "{direction=#{self.direction}}" end def self.from_xml(node) return OrderOption.new(node.attributes['Direction']) end end # Represents the ORDER BY part of a query. class Order attr_reader :expression, :order def initialize(expression, order=nil) self.expression = expression self.order = order end def expression=(e) VOTables::VOTable::Misc::TypeCheck.new(e, ScalarExpression).check() @expression = e end def order=(o) begin VOTables::VOTable::Misc::TypeCheck.new(o, OrderOption).check() rescue VOTables::VOTable::Misc::TypeException @order = OrderOption.new(o) else @order = o end end def to_s "{expression=#{self.expression},order=#{self.order}}" end def self.from_xml(node) expr_node = REXML::XPath.first(node, 'Expression') expr = Expression.from_xml(expr_node) order_option_node = REXML::XPath.first(node, 'Order') order_option = OrderOption.from_xml(order_option_node) return Order.new(expr, order_option) end end # List of expressions in which order the results should be provided. class OrderExpression attr_reader :items def initialize(items) self.items = items end def items=(is) raise "Specify at least one item" if is.size < 1 @items = [] is.each do |i| VOTables::VOTable::Misc::TypeCheck.new(i, Order).check() @items.push(i) end end def to_s items = self.items.collect{|x| x.to_s}.join('|') "{items=#{items}}" end def self.from_xml(node) items = [] node.elements.each('Item') do |item_node| item = Item.from_xml(item_node) items.push(item) end return OrderExpression.new(items) end end # Represents a list of constants provided for a SQL IN expression. class ConstantListSet < InclusionSet attr_reader :items def initialize(items) self.items = items end def items=(is) raise "Specify at least one item" if is.size < 1 @items = [] is.each do |i| begin VOTables::VOTable::Misc::TypeCheck.new(i, LiteralType).check() rescue VOTables::VOTable::Misc::TypeException if i.is_a?(String) @items.push(StringType.new(i)) elsif i.is_a?(Integer) @items.push(IntegerType.new(i)) elsif i.is_a?(Float) @items.push(RealType.new(i)) else raise "Item must be of type LiteralType" end else @items.push(i) end end end def to_s items = self.items.collect{|x| x.to_s}.join('|') "{items=#{items}}" end def self.from_xml(node) item_list = [] node.elements.each('Item') do |item_node| item = Item.from_xml(item_node) item_list.push(Item.from_xml(item_node)) end return ConstantListSet.new(item_list) end end # Represents SQL IN expression. class InclusiveSearch < Search attr_reader :expression, :set def initialize(expression, set) self.expression = expression self.set = set end def expression=(e) VOTables::VOTable::Misc::TypeCheck.new(e, ScalarExpression).check() @expression = e end def set=(s) VOTables::VOTable::Misc::TypeCheck.new(s, InclusionSet).check() @set = s end def to_s "{expression=#{self.expression},set=#{self.set}}" end def self.from_xml(node) expr_node = REXML::XPath.first(node, 'Expression') expr = Expression.from_xml(expr_node) set_node = REXML::XPath.first(node, 'Set') set = Set.from_xml(set_node) return InclusiveSearch.new(expr, set) end end # The SELECT part of a query. class Select attr_reader :allow, :restrict, :selection_list, :in_to, :from, :where, :group_by, :having, :order_by attr_accessor :start_comment, :end_comment def initialize(selection_list, allow=nil, restrict=nil, in_to=nil, from=nil, where=nil, group_by=nil, having=nil, order_by=nil, start_comment=nil, end_comment=nil) self.selection_list = selection_list self.allow = allow self.restrict = restrict self.in_to = in_to self.from = from self.where = where self.group_by = group_by self.having = having self.order_by = order_by self.start_comment = start_comment self.end_comment = end_comment end def selection_list=(sl) VOTables::VOTable::Misc::TypeCheck.new(sl, SelectionList).check() @selection_list = sl end def allow=(a) begin VOTables::VOTable::Misc::TypeCheck.new(a, SelectionOption).check() rescue VOTables::VOTable::Misc::TypeException @allow = SelectionOption.new(a) else @allow = a end end def restrict=(r) begin VOTables::VOTable::Misc::TypeCheck.new(r, SelectionLimit).check() rescue VOTables::VOTable::Misc::TypeException @restrict = SelectionLimit.new(r) else @restrict = r end end def in_to=(it) begin VOTables::VOTable::Misc::TypeCheck.new(it, Into).check() rescue VOTables::VOTable::Misc::TypeException @in_to = Into.new(it) else @in_to = it end end def from=(f) VOTables::VOTable::Misc::TypeCheck.new(f, From).check() @from = f end def where=(w) VOTables::VOTable::Misc::TypeCheck.new(w, Where).check() @where = w end def group_by=(gb) VOTables::VOTable::Misc::TypeCheck.new(gb, GroupBy).check() @group_by = gb end def having=(h) VOTables::VOTable::Misc::TypeCheck.new(h, Having).check() @having = h end def order_by=(ob) VOTables::VOTable::Misc::TypeCheck.new(ob, OrderExpression).check() @order_by = ob end def to_s "{allow=#{self.allow},restrict=#{self.restrict},selection_list=#{self.selection_list}," + "in_to=#{self.in_to},from=#{self.from},where=#{self.where},group_by=#{self.group_by}," + "having=#{self.having},order_by=#{self.order_by},start_comment=#{self.start_comment}," + "end_comment=#{self.end_comment}}" end def to_file(file_path) file = File.new(file_path, 'w') file.syswrite(self.to_adqlx) file.close() end def self.from_xml(node) # SelectionList sl_node = REXML::XPath.first(node, 'SelectionList') or raise "No SelectionList element" selection_list = SelectionList.from_xml(sl_node) # Allow allow_node = REXML::XPath.first(node, 'Allow') allow = nil allow = Allow.from_xml(allow_node) if allow_node # Restrict restrict_node = REXML::XPath.first(node, 'Restrict') restrict = nil restrict = Restrict.from_xml(restrict_node) if restrict_node # InTo into_node = REXML::XPath.first(node, 'InTo') into = nil into = InTo.from_xml(into_node) if into_node # From from_node = REXML::XPath.first(node, 'From') from = nil from = From.from_xml(from_node) if from_node # Where where_node = REXML::XPath.first(node, 'Where') where = nil where = Where.from_xml(where_node) if where_node # GroupBy groupby_node = REXML::XPath.first(node, 'GroupBy') groupby = nil groupby = GroupBy.from_xml(groupby_node) if groupby_node # Having having_node = REXML::XPath.first(node, 'Having') having = nil having = Having.from_xml(having_node) if having_node # OrderBy orderby_node = REXML::XPath.first(node, 'OrderBy') orderby = nil orderby = OrderBy.from_xml(orderby_node) if orderby_node # StartComment start_comment_node = REXML::XPath.first(node, 'StartComment') start_comment = nil start_comment = StartComment.from_xml(start_comment_node) if start_comment_node # EndComment end_comment_node = REXML::XPath.first(node, 'EndComment') end_comment = nil end_comment = EndComment.from_xml(end_comment_node) if end_comment_node return Select.new(selection_list, allow, restrict, into, from, where, groupby, having, orderby, start_comment, end_comment) end end # Represents user defined function expressions. class UserDefinedFunction < ScalarExpression attr_accessor :name attr_reader :params def initialize(name, params=nil) self.name = name self.params = params end def params=(ps) if ps raise "Provide at least 1 parameter" if ps.size < 1 @params = [] ps.each do |p| VOTables::VOTable::Misc::TypeCheck.new(p, ScalarExpression) @params.push(p) end end end def to_s params = self.params.collect{|x| x.to_s}.join('|') "{name=#{self.name},params=#{params}}" end def self.from_xml(node) name = REXML::XPath.first(node, 'Name').text params = [] node.elements.each('Params') do |param_node| params.push(ScalarExpression.from_xml(param_node)) end params = nil if params.size < 1 return UserDefinedFunction.new(name, params) end end # Denotes the type of a Join operation. class JointTableQualifier attr_reader :value @@joins = ['LEFT_OUTER', 'RIGHT_OUTER', 'FULL_OUTER', 'INNER', 'CROSS'] def initialize(value, join_list=nil) @join_list = join_list || @@joins self.value = value end def value=(v) if @join_list.include?(v) @value = v else raise "Join type is not valid. Use one of: " + @join_list.join(', ') end end def to_s "{value=#{self.value}}" end def self.from_xml(node) return JointTableQualifier.new(node.text) end end # Represents SQL JOIN expression. class JoinTable < FromTable attr_reader :qualifier, :tables, :condition def initialize(qualifier, tables, condition) self.qualifier = qualifier self.tables = tables self.condition = condition end def qualifier=(q) begin VOTables::VOTable::Misc::TypeCheck.new(q, JointTableQualifier).check() rescue VOTables::VOTable::Misc::TypeException @qualifier = JointTableQualifier.new(q) else @qualifier = q end end def tables=(ts) VOTables::VOTable::Misc::TypeCheck.new(ts, ArrayOfFromTable).check() @tables = ts end def condition=(c) VOTables::VOTable::Misc::TypeCheck.new(c, ComparisonPred).check() @condition = c end def to_s "{qualifier=#{self.qualifier},tables=#{self.tables},condition=#{self.condition}}" end def self.from_xml(node) qual_node = REXML::XPath.first(node, 'Qualifier') qual = Qualifier.from_xml(qual_node) tables_node = REXML::XPath.first(node, 'Tables') tables = Tables.from_xml(tables_node) cond_node = REXML::XPath.first(node, 'Condition') cond = Condition.from_xml(cond_node) return JoinTable.new(qual, tables, cond) end end # Represents an array of tables in the from expression.def expression. class ArrayOfFromTable attr_reader :from_tables def initialize(tables) self.from_tables = tables end def from_tables=(fts) raise "Provide at least one table" if fts.size < 1 @from_tables = [] if fts fts.each do |ft| VOTables::VOTable::Misc::TypeCheck.new(ft, FromTable) @from_tables.push(ft) end end end def to_s tables = self.from_tables.collect{|x| x.to_s}.join('|') "{from_tables=#{tables}}" end def self.from_xml(node) from_tables = [] node.elements.to_a('Table').each do |ftbl| from_tables.push(FromTable.from_xml(ftbl)) end return ArrayOfFromTable.new(from_tables) end end end end