# # Generate HTML-highlighted CQL from an ActiveFacts vocabulary. # Copyright (c) 2008 Clifford Heath. Read the LICENSE file. # # The text generated here is pre-formatted, and in spans haing the following styles: # keyword: ORM2 standard colour is #00C (blue) # concept: ORM2 standard concept is #808 (purple) # copula: ORM2 standard concept is #060 (green) # require 'activefacts/vocabulary' require 'activefacts/api' require 'activefacts/generate/ordered' require 'activefacts/generate/cql' module ActiveFacts module Generate class CQL class HTML < CQL def initialize(vocabulary, *options) super end def puts s super(s.gsub(/[,;]/) do |p| keyword p; end) end def keyword(str) "#{str}" end def concept(str) "#{str}" end def copula(str) "#{str}" end def vocabulary_start(vocabulary) puts %q{
} puts "#{keyword "vocabulary"} #{concept(vocabulary.name)};\n\n" end def vocabulary_end puts %q{} end def value_type_banner puts "/*\n * Value Types\n */" end def value_type_dump(o) return unless o.supertype # An imported type if o.name == o.supertype.name # In ActiveFacts, parameterising a ValueType will create a new datatype # throw Can't handle parameterized value type of same name as its datatype" if ... end parameters = [ o.length != 0 || o.scale != 0 ? o.length : nil, o.scale != 0 ? o.scale : nil ].compact parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()" puts "#{concept o.name} #{keyword "is defined as"} #{concept o.supertype.name + parameters }#{ if (o.value_restriction) keyword("restricted to")+ o.value_restriction.all_allowed_range.map{|ar| # REVISIT: Need to display as string or numeric according to type here... min = ar.value_range.minimum_bound max = ar.value_range.maximum_bound range = (min ? min.value : "") + (min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "") keyword range }*", " else "" end };" end def append_ring_to_reading(reading, ring) reading << keyword(" [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]") end def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings) identifying_role_names = identifying_roles.map{|role| preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr| reading_rr.role == role } role_words = [] # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name role_name = role.role_name role_name = nil if role_name == "" # debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}" if (role.fact_type.all_role.size == 1) # REVISIT: Guard against unary reading containing the illegal words "and" and "where". role.fact_type.default_reading # Need whole reading for a unary. elsif (role_name) role_name else role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != "" role_words << preferred_role_ref.role.concept.name role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != "" role_words.compact*"-" end } # REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump? # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not. # Detect standard reference-mode scenarios ft = identifying_facts[0] fact_constraints = nil if identifying_facts.size == 1 and entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and value_role = ft.all_role[1-n] and value_name = value_role.concept.name and residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and residual != '' and residual != value_name # The EntityType is identified by its association with a single ValueType # whose name is an extension (the residual) of the EntityType's name. # Detect standard reference-mode readings: forward_reading = reverse_reading = nil ft.all_reading.each do |reading| if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/ if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role forward_reading = reading else reverse_reading = reading end elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/ if reading.role_sequence.all_role_ref[$1.to_i].role == value_role reverse_reading = reading else forward_reading = reading end end end debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading # If we didn't find at least one of the standard readings, don't use a refmode: if (forward_reading || reverse_reading) # Elide the constraints that would have been emitted on those readings. # If there is a UC that's not in the standard form for a reference mode, # we have to emit the standard reading anyhow. fact_constraints = @presence_constraints_by_fact[ft] fact_constraints.each do |pc| if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1) # It's a uniqueness constraint, and will be regenerated @constraints_used[pc] = true end end @fact_types_dumped[ft] = true # Figure out whether any non-standard readings exist: other_readings = ft.all_reading - [forward_reading] - [reverse_reading] debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0 fact_text = other_readings.map do |reading| expanded_reading(reading, fact_constraints, true) end*",\n\t" return keyword(" identified by its ") + concept(residual) + (fact_text != "" ? keyword(" where\n\t") + fact_text : "") end end identifying_facts.each{|f| @fact_types_dumped[f] = true } @identifying_fact_text = identifying_facts.map{|f| fact_readings_with_constraints(f, fact_constraints) }.flatten*",\n\t" keyword(" identified by ") + identifying_role_names.map{|n| concept n} * keyword(" and ") + keyword(" where\n\t") + @identifying_fact_text end def show_frequency role, constraint # REVISIT: Need to also colorize the adjectives here: [ constraint ? keyword(constraint.frequency) : nil, concept(role.concept.name) ] end def entity_type_banner puts(keyword("/*\n * Entity Types\n */")) end def fact_readings(fact_type) constrained_fact_readings = fact_readings_with_constraints(fact_type) constrained_fact_readings*",\n\t" end def subtype_dump(o, supertypes, pi) print "#{concept o.name} #{keyword "is a kind of"} #{ o.supertypes.map(&:name).map{|n| concept n}*keyword(", ") }" if pi print identified_by(o, pi) end # If there's a preferred_identifier for this subtype, identifying readings were emitted if o.fact_type print( (pi ? "," : keyword(" where")) + "\n\t" + fact_readings(o.fact_type) ) end puts ";\n" end def non_subtype_dump(o, pi) print "#{concept(o.name)} #{keyword "is"}" + identified_by(o, pi) print(keyword(" where\n\t") + fact_readings(o.fact_type)) if o.fact_type puts ";\n" end def fact_type_dump(fact_type, name) @identifying_fact_text = nil if (o = fact_type.entity_type) print "#{concept o.name} #{keyword "is"}" if !o.all_type_inheritance_by_subtype.empty? print(keyword(" a kind of ") + o.supertypes.map(&:name).map{|n| concept n}*", ") end # Alternate identification of objectified fact type? primary_supertype = o.supertypes[0] pi = fact_type.entity_type.preferred_identifier if pi && primary_supertype && primary_supertype.preferred_identifier != pi print identified_by(o, pi) print ";\n" end end unless @identifying_fact_text print(keyword(" where\n\t")) if o puts(fact_readings(fact_type)+";") end end def fact_type_banner puts keyword("/*\n * Fact Types\n */") end def constraint_banner puts keyword("/*\n * Constraints:\n */") end def dump_presence_constraint(c) roles = c.role_sequence.all_role_ref.map{|rr| rr.role } # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form: # each Bug SOME Tester logged THAT Bug; players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq puts \ "#{keyword "each #{players.size > 1 ? "combination " : ""}"}"+ "#{players.map{|n| concept n}*", "} "+ "#{keyword "occurs #{c.frequency} time in"}\n\t"+ "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" + ";" end def dump_set_constraint(c) # REVISIT exclusion: every