diff options
| author | Jérémy Zurcher <jeremy@asynk.ch> | 2011-10-22 23:41:26 +0200 | 
|---|---|---|
| committer | Jérémy Zurcher <jeremy@asynk.ch> | 2011-10-22 23:41:26 +0200 | 
| commit | 9cb9019e8ddcc7eb6d19921ab3caf7b16ab6cb45 (patch) | |
| tree | 4a16e7faadea905ea3b7decc7c4931af5676def4 | |
| parent | b045a528ff48795d630058b349842da79be8b4a0 (diff) | |
| download | bin-9cb9019e8ddcc7eb6d19921ab3caf7b16ab6cb45.zip bin-9cb9019e8ddcc7eb6d19921ab3caf7b16ab6cb45.tar.gz  | |
add dia2pg.rb
| -rwxr-xr-x | dia2pg.rb | 338 | 
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 +#  | 
