summaryrefslogtreecommitdiffstats
path: root/dia2pg.rb
diff options
context:
space:
mode:
Diffstat (limited to 'dia2pg.rb')
-rwxr-xr-xdia2pg.rb338
1 files changed, 338 insertions, 0 deletions
diff --git a/dia2pg.rb b/dia2pg.rb
new file mode 100755
index 0000000..36c4299
--- /dev/null
+++ b/dia2pg.rb
@@ -0,0 +1,338 @@
+#! /usr/bin/env ruby
+# -*- coding: UTF-8 -*-
+
+#----------------------------------------------------------------------------
+#
+# File : dia2pg.rb
+# Author : Jérémy Zurcher <jeremy@asynk.ch>
+# Date : 24/08/10
+# License :
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#----------------------------------------------------------------------------
+#
+require 'ostruct'
+require 'optparse'
+#
+class OpenStruct
+ def to_h; @table end
+end
+#
+options = OpenStruct.new
+#
+options.verbose = false
+options.input = nil
+options.drop = false
+options.create = false
+options.odids = false
+options.user = 'postgres'
+options.tbl_prefix = nil
+#
+opts = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options]"
+ opts.on( "-i", "--input FILENAME", "input file" ) { |n| options.input = n }
+ opts.on( "-V", "--verbose", "produce verbose output" ) { options.verbose = true }
+ opts.on( "-d", "--drop", "generate drop table statements" ) { options.drop = true }
+ opts.on( "-c", "--create", "generate create table statements" ) { options.create = true }
+ opts.on( "-u", "--user USER", "set table owner's" ) { |n| options.user = n }
+ opts.on( "-p", "--prefix PREFIX", "add a table prefix" ) { |n| options.tbl_prefix = n }
+ opts.on( "-o", "--oids", "set OIDS" ) { options.oids = true }
+end
+#
+opts.parse!(ARGV)
+#
+if options.input.nil?
+ $stderr << "missing input file argument\n"
+ exit 1
+end
+#
+options.tbl_prefix = ( options.tbl_prefix ? options.tbl_prefix+(options.tbl_prefix[-1]=='_' ? '' : '_') : '' )
+#
+HEADER="\n -- generated with dia2pg\n"
+SEP="/**********************************************************************/"
+DATABASE = {}
+RELATIONS = []
+#
+class Table
+ #
+ def initialize opts
+ @opts = opts
+ @attributes = []
+ @attr_prefix = nil
+ @tbl_prefix = nil
+ end
+ #
+ attr_reader :attr_prefix
+ attr_writer :name
+ attr_accessor :id
+ #
+ def to_s
+ r = "Table #{@id} - #{@name} - #{@comment}\n"
+ @attributes.inject(r) do |r,a| r<<a.to_s end
+ end
+ #
+ def comment= c
+ @comment = c
+ if c=~/attr_prefix=(.*)$/
+ @attr_prefix = $1
+ @attr_prefix+='_' if @attr_prefix[-1]!='_'
+ end
+ if c=~/tbl_prefix=(.*)$/
+ @tbl_prefix = $1
+ @tbl_prefix+='_' if @tbl_prefix[-1]!='_'
+ end
+ end
+ #
+ def no_export?
+ return false if @comment.nil?
+ @comment=~/no_export=1/
+ end
+ #
+ def << a
+ @attributes << a
+ end
+ #
+ def attr n
+ @attributes[((n-10)/2)-1]
+ end
+ #
+ def name
+ ( @tbl_prefix ? @tbl_prefix+@name : @opts.tbl_prefix+@name )
+ end
+ #
+ def to_sql
+ r = ''
+ if @attr_prefix or @opts.tbl_prefix
+ r << "-- dia2pg"
+ r << " tbl_prefix=#{@opts.tbl_prefix}" if @opts.tbl_prefix
+ r << " attr_prefix=#{@attr_prefix}" if @attr_prefix
+ r << "\n"
+ end
+ r << "CREATE TABLE #{name}\n(\n"
+ pk = []
+ uq = { :all=>[] }
+ @attributes.each do |attr|
+ sql = attr.to_sql
+ next if sql.nil?
+ r << sql
+ pk << attr.real_name if attr.primary_key
+ if attr.unique and not attr.primary_key
+ if attr.comment=~/U./
+ uq[attr.comment] ||= []
+ uq[attr.comment]<< attr.real_name
+ else
+ uq[:all] << attr.real_name
+ end
+ end
+ end
+ uq.each do |k,v| r << " UNIQUE(#{v.join ','}),\n" unless v.empty? end
+ r << " CONSTRAINT pk_#{name} PRIMARY KEY (#{pk.join ','})\n" if pk.length>0
+ r.sub!(/,\n$/,"\n")
+ r << ")\nWITH (\n OIDS=#{@opts.oids ? 'TRUE' : 'FALSE'}\n);\n"
+ r << "ALTER TABLE #{name} OWNER TO #{@opts.user};\n"
+ r
+ end
+ #
+ def drop
+ puts "DROP TABLE IF EXISTS #{name} CASCADE;\n"
+ end
+ #
+end
+#
+class Attribute
+ #
+ def initialize tbl
+ @tbl = tbl
+ end
+ #
+ attr_reader :name, :primary_key, :unique, :comment
+ attr_writer :name, :type, :comment, :primary_key, :nullable, :unique
+ #
+ def to_s
+ " # #{@name} - #{@type} - #{@comment} - #{@primary_key} - #{@nullable} - #{@unique}\n"
+ end
+ #
+ def foreign?
+ @type=~/foreign/
+ end
+ #
+ def no_rename?
+ return false if @comment.nil?
+ @comment=~/no_rename=1/
+ end
+ #
+ def no_prefix?
+ return false if @comment.nil?
+ @comment=~/no_prefix=1/
+ end
+ #
+ def no_export?
+ return false if @comment.nil?
+ @comment=~/no_export=1/
+ end
+ #
+ def real_name
+ if foreign? and not no_rename?
+ rl = RELATIONS.find { |r| r.tbl_to==@tbl and r.attr_to.name==@name }
+ if rl.nil?
+ $stderr << "MISSING RELATION FOR FOREIGN KEY table:#{@tbl.name} attribute:#{@name}\n"
+ raise Exception .new "MISSING RELATION FOR FOREIGN KEY table:#{@tbl.name} attribute:#{@name}\n"
+ end
+ if rl.tbl_from==@tbl
+ ( (@tbl.attr_prefix.nil? or no_prefix?) ? @name : @tbl.attr_prefix+@name )
+ else
+ rl.attr_from.real_name
+ end
+ else
+ ( (@tbl.attr_prefix.nil? or no_prefix?) ? @name : @tbl.attr_prefix+@name )
+ end
+ end
+ #
+ def type
+ foreign? ? 'integer' : @type
+ end
+ #
+ def to_sql
+ return if no_export?
+ r = " #{format "%-35s", real_name}"
+ if foreign?
+ rl = RELATIONS.find { |r| r.tbl_to==@tbl and r.attr_to.name==@name }
+ if rl.nil?
+ $stderr << "MISSING RELATION FOR FOREIGN KEY #{@tbl.name} #{@name}\n"
+ raise Excpetion.new "MISSING RELATION FOR FOREIGN KEY #{@tbl.name} #{@name}\n"
+ else
+ r << "integer REFERENCES #{rl.tbl_from.name}(#{rl.attr_from.real_name})"
+ r << " NOT NULL" if not @nullable
+ end
+ elsif @nullable
+ r << type
+ else
+ r << "#{type} NOT NULL"
+ end
+ r << ",\n"
+ end
+ #
+end
+#
+class Relation
+ #
+ attr_accessor :tbl_from, :attr_from, :m_from, :tbl_to, :attr_to, :m_to
+ #
+ def to_s
+ " # #{tbl_from.name}.#{attr_from.name} (#{m_from}) => #{tbl_to.name}.#{attr_to.name} (#{m_to})"
+ end
+ #
+end
+#
+require 'zlib'
+require 'nokogiri'
+doc = Nokogiri::XML( Zlib::GzipReader.new( File.open(options.input) ).read )
+#
+doc.xpath('//dia:object[@type="Database - Table"]').each do |node|
+ tbl = Table.new options
+ tbl.id = node.xpath('@id').to_s
+ tbl.name = node.xpath('dia:attribute[@name="name"]/dia:string').first.content[1..-2]
+ $stderr << "# parse #{tbl.id} #{tbl.name}\n" if options.verbose
+ tbl.comment = node.xpath('dia:attribute[@name="comment"]/dia:string').first.content[1..-2]
+# tbl.comment = $1.to_s
+ node.xpath('dia:attribute[@name="attributes"]').each do |el|
+ el.xpath('dia:composite[@type="table_attribute"]').each do |a|
+ attr = Attribute.new tbl
+ attr.name = a.xpath('dia:attribute[@name="name"]/dia:string').first.content[1..-2]
+ attr.type = a.xpath('dia:attribute[@name="type"]/dia:string').first.content[1..-2]
+ $stderr << " # #{attr.name} #{attr.type}\n" if options.verbose
+ attr.comment = a.xpath('dia:attribute[@name="comment"]/dia:string').first.content[1..-2]
+ attr.primary_key = a.xpath('dia:attribute[@name="primary_key"]/dia:boolean/@val').to_s=='true'
+ attr.nullable = a.xpath('dia:attribute[@name="nullable"]/dia:boolean/@val').to_s=='true'
+ attr.unique = a.xpath('dia:attribute[@name="unique"]/dia:boolean/@val').to_s=='true'
+ tbl << attr
+ end
+ end
+ DATABASE[tbl.id]=tbl
+end
+# DATABASE REFERENCES
+doc.xpath('//dia:object[@type="Database - Reference"]').each do |node|
+ r = Relation.new options
+ r.m_from = node.xpath('dia:attribute[@name="start_point_desc"]/dia:string').first.content[1..-2]
+ r.m_to = node.xpath('dia:attribute[@name="end_point_desc"]/dia:string').first.content[1..-2]
+ r.tbl_from = DATABASE[node.xpath('dia:connections/dia:connection[@handle="0"]/@to').to_s]
+ r.tbl_to = DATABASE[node.xpath('dia:connections/dia:connection[@handle="1"]/@to').to_s]
+ next if r.tbl_from.nil? or r.tbl_to.nil?
+ r.attr_from = r.tbl_from.attr node.xpath('dia:connections/dia:connection[@handle="0"]/@connection').to_s.to_i
+ r.attr_to = r.tbl_to.attr node.xpath('dia:connections/dia:connection[@handle="1"]/@connection').to_s.to_i
+ $stderr << "# connection : #{r.to_s}\n" if options.verbose
+ RELATIONS << r
+end
+#
+doc.xpath('//dia:object[@type="UML - Association"]').each do |node|
+ r = Relation.new
+ node.xpath('dia:attribute[@name="name"]/dia:string').first.content[1..-2]
+ r.m_from = node.xpath('dia:attribute[@name="multipicity_a"]/dia:string').first.content[1..-2]
+ r.m_to = node.xpath('dia:attribute[@name="multipicity_b"]/dia:string').first.content[1..-2]
+ node.xpath('dia:attribute[@name="role_a"]/dia:string').first.content[1..-2]
+ node.xpath('dia:attribute[@name="role_b"]/dia:string').first.content[1..-2]
+ r.tbl_from = DATABASE[node.xpath('dia:connections/dia:connection[@handle="0"]/@to').to_s]
+ r.tbl_to = DATABASE[node.xpath('dia:connections/dia:connection[@handle="1"]/@to').to_s]
+ r.attr_from = r.tbl_from.attr node.xpath('dia:connections/dia:connection[@handle="0"]/@connection').to_s.to_i
+ r.attr_to = r.tbl_to.attr node.xpath('dia:connections/dia:connection[@handle="1"]/@connection').to_s.to_i
+ RELATIONS << r
+end
+#
+TBL_DEBS= {}
+#
+RELATIONS.each do |r|
+ TBL_DEBS[r.tbl_to.id] ||=[]
+ TBL_DEBS[r.tbl_to.id] << r.tbl_from.id
+end
+#
+FLUSHED = []
+#
+def flush tbl, l
+ if l==DATABASE.length
+ $stderr << "Can't resolve dependecy loop\n"
+ exit 1
+ end
+ return if FLUSHED.include? tbl.id or tbl.no_export?
+ if not TBL_DEBS[tbl.id].nil?
+ TBL_DEBS[tbl.id].each do |t|
+ # not self depend
+ flush( DATABASE[t], l+1) if not t==tbl.id
+ end
+ end
+ puts SEP
+ puts tbl.to_sql
+ puts ""
+ FLUSHED << tbl.id
+end
+#
+puts HEADER
+if options.drop
+ DATABASE.each do |k,tbl|
+ tbl.drop unless tbl.no_export?
+ end
+ puts ''
+end
+if options.create
+ DATABASE.each do |k,tbl|
+ flush tbl, 0
+ end
+end
+#