nwn-lib 0.4.11 → 0.4.12

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -323,3 +323,32 @@ Bernhard Stoeckner <elven@swordcoast.net> (7):
323
323
  A mostly-maintenance release with some API changes. The most invasive changes
324
324
  are some method renames. New are some sane default values for Struct#add_*,
325
325
  and a few more specs. YAML now sets correct .element references.
326
+
327
+ === 0.4.12
328
+ Bernhard Stoeckner (18):
329
+ Gff read/write API: documentation, Handler#dump always returns bytes written
330
+ Settings: iconv: refactor into methods
331
+ data_type_spec: refactor to be more concise
332
+ Gff::Struct#data_type=: only emit debug message when new type is non-nil
333
+ Gff::Handler::JSON: emit terminating newline regardless of :pretty_json
334
+ JSON: infer data_version like yaml, omit if DEFAULT
335
+ Gff: error if registering format of same name twice, reverse FileFormatRX hash
336
+ Gff::Struct: reparenting struct emits debug message only if element != nil
337
+ Gff::Reader: allow reading of arbitary Gff version files
338
+ Gff::Struct: data_version allow more freedom in inferring, update spec
339
+ NWN::Gff: custom xml data format, nwntools.sf.net modpacker read/write support
340
+ Rakefile: spec output in spec-style format
341
+ Rakefile: remove obsolete rubyforge target
342
+ bin/nwn-gff: --out-encoding specified output encoding
343
+ NWN.setting: return default values for setting new values too
344
+ spec_helper: set $options to unbreak kivinen spec
345
+ NWN.log_debug: non-lib frame if available, :debug_trace prints full traces
346
+ Gff::Field: fix data ranges for int64, dword64
347
+ 0.4.12-rel
348
+
349
+ A few new features and some bugfixes this release:
350
+ - more concise yaml dumps (but backwards-compatible)
351
+ - some iconv fixes
352
+ - dword64 and int64 data ranges now correct
353
+ - support for parsing/dumping modpacker XML
354
+ - support for parsing/dumping a custom, cleaner XML format
data/README CHANGED
@@ -22,6 +22,8 @@ They should work with NWN2 just as well, since the file format specifications di
22
22
  * yaml presentation of data
23
23
  * json presentation of data (with proper UTF-8 conversion)
24
24
  * ruby-native marshalling of gff data
25
+ * a compact native xml format
26
+ * nwntools.sf.net-style xml files ("modpacker")
25
27
 
26
28
  ===== just write, for now:
27
29
  * kivinen-style ("gffprint.pl") presentation of data
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "nwn-lib"
12
- VERS = "0.4.11"
12
+ VERS = "0.4.12"
13
13
  CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
14
14
  RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
15
15
  'nwn-lib: a ruby library for accessing NWN resource files', \
@@ -68,17 +68,12 @@ task :uninstall => [:clean] do
68
68
  sh %{sudo gem1.8 uninstall #{NAME}}
69
69
  end
70
70
 
71
- desc "Upload nwn-lib gem to rubyforge"
72
- task :release => [:package] do
73
- sh %{rubyforge add_release nwn-lib #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
74
- sh %{rubyforge add_file nwn-lib #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
75
- end
76
-
77
71
  require "spec/rake/spectask"
78
72
 
79
73
  desc "Run specs with coverage"
80
74
  Spec::Rake::SpecTask.new("spec") do |t|
81
75
  t.spec_files = FileList["spec/*_spec.rb"]
76
+ t.spec_opts = ["--format s"]
82
77
  t.rcov = true
83
78
  end
84
79
 
@@ -86,11 +81,13 @@ desc "Run specs without coverage"
86
81
  task :default => [:spec_no_cov]
87
82
  Spec::Rake::SpecTask.new("spec_no_cov") do |t|
88
83
  t.spec_files = FileList["spec/*_spec.rb"]
84
+ t.spec_opts = ["--format s"]
89
85
  end
90
86
 
91
87
  desc "Run rcov only"
92
88
  Spec::Rake::SpecTask.new("rcov") do |t|
93
89
  t.spec_files = FileList["spec/*_spec.rb"]
90
+ t.spec_opts = ["--format s"]
94
91
  t.rcov = true
95
92
  end
96
93
 
data/SETTINGS CHANGED
@@ -24,6 +24,11 @@ resource files you are working with.
24
24
  If you want to suppress them for some reason, set NWN_LIB_DEBUG to "0" or "off". Any
25
25
  other value will indicate that you want to see the messages.
26
26
 
27
+ == NWN_LIB_DEBUG_TRACES
28
+
29
+ Set to non-nil to print full stack traces for each debug message emitted by NWN_LIB_DEBUG.
30
+ Default is not to print them, just the first non-library-frame.
31
+
27
32
  == NWN_LIB_RESREF16
28
33
 
29
34
  Set this to "1" if you are sure that you are only working with NWN1 files. This will
@@ -8,6 +8,7 @@ Thread.abort_on_exception = true
8
8
  $options = {
9
9
  :backup => nil,
10
10
  :encoding => nil,
11
+ :out_encoding => nil,
11
12
  :force => false,
12
13
  :infile => '-',
13
14
  :outfile => '-',
@@ -53,6 +54,10 @@ begin OptionParser.new do |o|
53
54
  "files are encoded in" do |e|
54
55
  $options[:encoding] = e
55
56
  end
57
+ o.on "--out-encoding ENCODING", "sets the used output encoding",
58
+ "(defaults to UTF-8)" do |e|
59
+ $options[:out_encoding] = e
60
+ end
56
61
 
57
62
  o.on "-1", "--nwn1", "Allow 16 byte resrefs." do
58
63
  ENV['NWN_LIB_RESREF32'] = nil
@@ -115,9 +120,8 @@ end
115
120
  $options[:informat] or fail "No input format specified."
116
121
  $options[:outformat] or fail "No output format specified."
117
122
 
118
- if $options[:encoding]
119
- NWN.setting(:in_encoding, $options[:encoding])
120
- end
123
+ NWN.setting(:in_encoding, $options[:encoding]) if $options[:encoding]
124
+ NWN.setting(:out_encoding, $options[:out_encoding]) if $options[:out_encoding]
121
125
 
122
126
  if :auto == $options[:informat]
123
127
  $options[:informat] = NWN::Gff.guess_file_format($options[:infile].downcase)
@@ -15,4 +15,11 @@ rescue LoadError => e
15
15
  NWN.log_debug "json support not available, install json or json_pure to enable"
16
16
  end
17
17
  require 'nwn/kivinen_support'
18
+ begin
19
+ require 'xml'
20
+ require 'nwn/xml_support'
21
+ rescue LoadError => e
22
+ NWN.log_debug "nxml and modpacker support not available, install libxml-ruby to enable"
23
+ end
24
+
18
25
  require 'nwn/scripting'
@@ -17,6 +17,60 @@ module NWN
17
17
  # not exist.
18
18
  class GffPathInvalidError < RuntimeError; end
19
19
 
20
+ # A namesoace for all Gff file format handlers.
21
+ module Handler
22
+ # Registers a new format handler that can deal with file formats for nwn-lib gff handling.
23
+ #
24
+ # [+name+] The name of this format as a symbol. Must be unique.
25
+ # [+fileFormatRegexp+] A regular expression matching file extensions for auto-detection.
26
+ # [+klass+] A object that responds to load(io) and dump(gff,io). load(io) reads from io
27
+ # and always returns a NWN::Gff::Struct describing a root struct, dump(gff, io)
28
+ # dumps the gff root struct in the handlers format to io and returns the number
29
+ # of bytes written.
30
+ # [+reads+] Boolean, indicates if this handler can read it's format and return gff data.
31
+ # [+writes+] Boolean, indicates if this handler can emit gff data in it's format.
32
+ def self.register name, fileFormatRegexp, klass, reads = true, writes = true
33
+ raise ArgumentError, "Handler for #{name.inspect} already registered." if
34
+ NWN::Gff::InputFormats[name.to_sym] || NWN::Gff::OutputFormats[name.to_sym]
35
+ NWN::Gff::InputFormats[name.to_sym] = klass if reads
36
+ NWN::Gff::OutputFormats[name.to_sym] = klass if writes
37
+ NWN::Gff::FileFormatGuesses[name.to_sym] = fileFormatRegexp
38
+ end
39
+
40
+ module Gff
41
+ def self.load io
42
+ NWN::Gff::Reader.read(io)
43
+ end
44
+ def self.dump data, io
45
+ NWN::Gff::Writer.dump(data, io)
46
+ end
47
+ end
48
+
49
+ module Pretty
50
+ def self.dump data, io
51
+ old = $> ; $> = StringIO.new
52
+ pp data.box
53
+ sz = $>.pos
54
+ $>.seek(0)
55
+ io.write $>.read
56
+ $> = old
57
+ sz
58
+ end
59
+ end
60
+
61
+ module Marshal
62
+ def self.dump data, io
63
+ d = ::Marshal.dump(data)
64
+ io.write(d)
65
+ d.size
66
+ end
67
+
68
+ def self.load io
69
+ ::Marshal.load(io)
70
+ end
71
+ end
72
+ end
73
+
20
74
  # This hash lists all possible NWN::Gff::Field types.
21
75
  Types = {
22
76
  0 => :byte,
@@ -58,39 +112,22 @@ module NWN
58
112
  :double => 'd',
59
113
  }.freeze
60
114
 
61
- # Registers a new format handler that can deal with file formats for nwn-lib gff handling.
62
- def self.register_format_handler name, fileFormatRegexp, klass, reads = true, writes = true
63
- InputFormats[name.to_sym] = klass if reads
64
- OutputFormats[name.to_sym] = klass if writes
65
- FileFormatGuesses[fileFormatRegexp] = name.to_sym
66
- end
67
-
68
- class Handler
69
- def self.load io
70
- NWN::Gff::Reader.read(io)
71
- end
72
- def self.dump data, io
73
- NWN::Gff::Writer.dump(data, io)
74
- end
75
- end
76
-
77
- class Pretty
78
- def self.dump data, io
79
- old = $> ; $> = io ; pp data.box ; $> = old
80
- end
81
- end
82
-
83
115
  InputFormats = {}
84
116
  OutputFormats = {}
85
117
  FileFormatGuesses = {}
86
118
 
87
- register_format_handler :gff, /^(ut[cdeimpstw]|git|are|gic|mod|ifo|fac|ssf|dlg|itp|bic)$/, NWN::Gff::Handler
88
- register_format_handler :marshal, /^marshal$/, Marshal
89
- register_format_handler :pretty, /^$/, Pretty, false, true
119
+ Handler.register :gff, /^(ut[cdeimpstw]|git|are|gic|mod|ifo|fac|ssf|dlg|itp|bic)$/, NWN::Gff::Handler::Gff
120
+ Handler.register :marshal, /^marshal$/, NWN::Gff::Handler::Marshal
121
+ Handler.register :pretty, /^$/, NWN::Gff::Handler::Pretty, false, true
90
122
 
91
123
  def self.guess_file_format(filename)
92
124
  extension = File.extname(filename.downcase)[1..-1]
93
- FileFormatGuesses[FileFormatGuesses.keys.select {|key| extension =~ key}[0]]
125
+ matches = FileFormatGuesses.select {|fmt,rx| extension =~ rx }
126
+ if matches.size == 1
127
+ matches[0][0]
128
+ else
129
+ nil
130
+ end
94
131
  end
95
132
 
96
133
  def self.read(io, format)
@@ -144,9 +144,9 @@ module NWN::Gff::Field
144
144
  value.is_a?(Integer) && value >= 0 && value <= 0xffffffff
145
145
 
146
146
  when :int64
147
- value.is_a?(Integer) && value >= -0x800000000000 && value <= 0x7fffffffffff
147
+ value.is_a?(Integer) && value >= -0x8000000000000000 && value <= 0x7fffffffffffffff
148
148
  when :dword64
149
- value.is_a?(Integer) && value >= 0 && value <= 0xffffffffffff
149
+ value.is_a?(Integer) && value >= 0 && value <= 0xffffffffffffffff
150
150
 
151
151
  when :float, :double
152
152
  value.is_a?(Float)
@@ -228,10 +228,10 @@ module NWN::Gff::Field
228
228
  case element.field_type
229
229
  when :cexolocstr
230
230
  element.field_value.each {|x,y|
231
- element.field_value[x.to_i] = NWN::IconvNativeToGff.call.iconv(element.field_value.delete(x))
231
+ element.field_value[x.to_i] = NWN.iconv_native_to_gff(element.field_value.delete(x))
232
232
  }
233
233
  when :cexostr
234
- element.field_value = NWN::IconvNativeToGff.call.iconv(element.field_value)
234
+ element.field_value = NWN.iconv_native_to_gff(element.field_value)
235
235
 
236
236
  when :list
237
237
  element.field_value.each_with_index {|x,idx|
@@ -252,10 +252,10 @@ module NWN::Gff::Field
252
252
  case field_type
253
253
  when :cexolocstr
254
254
  t['value'].each {|x,y|
255
- t['value'][x] = NWN::IconvGffToNative.call.iconv(y)
255
+ t['value'][x] = NWN.iconv_gff_to_native(y)
256
256
  }
257
257
  when :cexostr
258
- t['value'] = NWN::IconvGffToNative.call.iconv(t['value'])
258
+ t['value'] = NWN.iconv_gff_to_native(t['value'])
259
259
  end
260
260
  t
261
261
  end
@@ -29,9 +29,6 @@ class NWN::Gff::Reader
29
29
  list_indices_offset, list_indices_count =
30
30
  @io.e_read(160, "header").unpack("a4a4 VV VV VV VV VV VV")
31
31
 
32
- raise GffError, "Unknown version #{version}; not a gff?" unless
33
- version == "V3.2"
34
-
35
32
  raise GffError, "struct offset at wrong place, not a gff?" unless
36
33
  struct_offset == 56
37
34
 
@@ -3,8 +3,8 @@
3
3
  module NWN::Gff::Struct
4
4
  DEFAULT_DATA_VERSION = "V3.2"
5
5
 
6
- # The file version. Usually "V3.2" for root structs,
7
- # and nil for sub-structs.
6
+ # The file version. Usually "V3.2". If not given in a source
7
+ # format, DEFAULT_DATA_VERSION is inferred and set for all structs.
8
8
  attr_accessor :data_version
9
9
 
10
10
  # GFF struct type. The default is 0xffffffff.
@@ -40,13 +40,14 @@ module NWN::Gff::Struct
40
40
 
41
41
  # Overrides the data type (used by the built-in file format readers).
42
42
  def data_type= k
43
- NWN.log_debug("Setting explicit data_type for parented element") if @element
43
+ k = nil if k == ""
44
+ NWN.log_debug("Setting explicit data_type for parented element") if k && @element
44
45
  @data_type = k
45
46
  end
46
47
 
47
48
  def element= e #:nodoc:
48
49
  @element = e
49
- NWN.log_debug("Re-parenting a struct with explicit data_type #{@data_type.inspect}") if @data_type
50
+ NWN.log_debug("Re-parenting a struct with explicit data_type #{@data_type.inspect}") if e && @data_type
50
51
  end
51
52
 
52
53
  # Dump this struct as GFF binary data.
@@ -62,7 +63,7 @@ module NWN::Gff::Struct
62
63
  #
63
64
  # You can pass a block to this method, which will receive the newly-created
64
65
  # Struct as the only argument.
65
- def self.new struct_id = 0xffffffff, data_type = nil, data_version = nil
66
+ def self.new struct_id = 0xffffffff, data_type = nil, data_version = DEFAULT_DATA_VERSION
66
67
  s = {}.extend(self)
67
68
  s.struct_id = struct_id
68
69
  s.data_type = data_type
@@ -228,6 +229,7 @@ module NWN::Gff::Struct
228
229
  o.struct_id = o.delete('__struct_id')
229
230
  o.data_type = o.delete('__data_type')
230
231
  o.data_version = o.delete('__data_version')
232
+ o.data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION
231
233
 
232
234
  NWN.log_debug("Unboxed without a root data type") if
233
235
  !parent && !o.data_type
@@ -250,7 +252,8 @@ module NWN::Gff::Struct
250
252
  })
251
253
  t.merge!({
252
254
  '__data_version' => self.data_version,
253
- }) if self.data_version && self.data_version != DEFAULT_DATA_VERSION
255
+ }) if self.data_version && self.data_version !=
256
+ NWN::Gff::Struct::DEFAULT_DATA_VERSION
254
257
  t.merge!({
255
258
  '__data_type' => self.data_type
256
259
  }) if @data_type
@@ -12,7 +12,7 @@ module NWN::Gff::Field
12
12
  end
13
13
  end
14
14
 
15
- module NWN::Gff::JSON
15
+ module NWN::Gff::Handler::JSON
16
16
  def self.load io
17
17
  json = if io.respond_to?(:to_str)
18
18
  io.to_str
@@ -26,12 +26,14 @@ module NWN::Gff::JSON
26
26
  end
27
27
 
28
28
  def self.dump struct, io
29
- if NWN.setting(:pretty_json)
30
- io.puts JSON.pretty_generate(struct)
29
+ d = if NWN.setting(:pretty_json)
30
+ ::JSON.pretty_generate(struct)
31
31
  else
32
- io.print JSON.generate(struct)
32
+ ::JSON.generate(struct)
33
33
  end
34
+ io.puts d
35
+ d.size + 1
34
36
  end
35
37
  end
36
38
 
37
- NWN::Gff.register_format_handler :json, /^json$/, NWN::Gff::JSON
39
+ NWN::Gff::Handler.register :json, /^json$/, NWN::Gff::Handler::JSON
@@ -1,4 +1,4 @@
1
- module NWN::Gff::Kivinen
1
+ module NWN::Gff::Handler::Kivinen
2
2
  def self.load io
3
3
  raise NotImplementedError, "Reading kivinen not supported"
4
4
  end
@@ -9,6 +9,7 @@ module NWN::Gff::Kivinen
9
9
  ret += "%s:\t%s\n" % [l, v]
10
10
  end
11
11
  io.puts ret
12
+ ret.size
12
13
  end
13
14
 
14
15
  # Parses +s+ as an arbitary GFF object and yields for each field found,
@@ -64,4 +65,4 @@ module NWN::Gff::Kivinen
64
65
  end
65
66
  end
66
67
 
67
- NWN::Gff.register_format_handler :kivinen, /^k(ivinen)?$/, NWN::Gff::Kivinen, false, true
68
+ NWN::Gff::Handler.register :kivinen, /^k(ivinen)?$/, NWN::Gff::Handler::Kivinen, false, true
@@ -2,7 +2,8 @@ require 'iconv'
2
2
 
3
3
  module NWN
4
4
  SETTING_DEFAULT_VALUES = {
5
- 'NWN_LIB_IN_ENCODING' => 'ISO-8859-1'
5
+ 'NWN_LIB_IN_ENCODING' => 'ISO-8859-1',
6
+ 'NWN_LIB_OUT_ENCODING' => 'UTF-8'
6
7
  }
7
8
 
8
9
  # This writes a internal warnings and debug messages to stderr.
@@ -17,9 +18,18 @@ module NWN
17
18
  # Do not print debug messages if explicitly turned off
18
19
  return false if [false, "off"].index(setting(:debug))
19
20
 
20
- pa = caller[0].to_s
21
- pa = pa[(pa.size - 36) .. -1] if pa.size > 36
22
- $stderr.puts "(nwn-lib) %s: %s" % [pa, msg]
21
+ if NWN.setting(:debug_traces)
22
+ $stderr.puts "(nwn-lib): %s" % [msg]
23
+ $stderr.puts " " + caller.join("\n ") + "\n"
24
+ else
25
+ dir = File.expand_path(File.dirname(File.expand_path(__FILE__)) + "/../../")
26
+ pa = caller.reject {|x| x.index(dir) }[0]
27
+ pa ||= caller[0]
28
+ pa ||= "(no frames)"
29
+ pa = pa[(pa.size - 36) .. -1] if pa.size > 36
30
+ $stderr.puts "(nwn-lib) %s: %s" % [pa, msg]
31
+ end
32
+
23
33
  true
24
34
  end
25
35
 
@@ -29,7 +39,7 @@ module NWN
29
39
  def self.setting sym, value = :_invalid_
30
40
  name = "NWN_LIB_#{sym.to_s.upcase}"
31
41
  if value != :_invalid_
32
- ret = ENV[name] == "0" ? false : ENV[name]
42
+ ret = setting(sym)
33
43
  ENV[name] = value.to_s if value != :_invalid_
34
44
  ret
35
45
  else
@@ -37,8 +47,25 @@ module NWN
37
47
  end
38
48
  end
39
49
 
40
- IconvGffToNative = proc { Iconv.new('utf-8', NWN.setting(:in_encoding)) }
41
- IconvNativeToGff = proc { Iconv.new(NWN.setting(:in_encoding), 'utf-8') }
50
+ IconvState = {} #:nodoc:
51
+
52
+ # Converts text from native format (such as json) to Gff (required by NWN).
53
+ def self.iconv_native_to_gff text
54
+ if IconvState[:in] != NWN.setting(:in_encoding) ||
55
+ IconvState[:out] != NWN.setting(:out_encoding)
56
+ IconvState[:in_i] = Iconv.new(NWN.setting(:in_encoding), NWN.setting(:out_encoding))
57
+ end
58
+ IconvState[:in_i].iconv(text)
59
+ end
60
+
61
+ # Converts text from Gff format to native/external, such as json (usually UTF-8).
62
+ def self.iconv_gff_to_native text
63
+ if IconvState[:in] != NWN.setting(:in_encoding) ||
64
+ IconvState[:out] != NWN.setting(:out_encoding)
65
+ IconvState[:out_i] = Iconv.new(NWN.setting(:out_encoding), NWN.setting(:in_encoding))
66
+ end
67
+ IconvState[:out_i].iconv(text)
68
+ end
42
69
  end
43
70
 
44
71
  NWN::TwoDA::Cache.setup NWN.setting("2da_location") if
@@ -0,0 +1,202 @@
1
+ class NWN::Gff::Handler::XML
2
+
3
+ private
4
+
5
+ def struct_to_xml struct
6
+ s = XML::Node.new('struct')
7
+ case @format
8
+ when :nxml
9
+ s['id'] = struct.struct_id.to_s
10
+ s['dataType'] = struct.data_type if struct.data_type
11
+ s['dataVersion'] = struct.data_version if
12
+ struct.data_version != NWN::Gff::Struct::DEFAULT_DATA_VERSION
13
+ when :modpacker
14
+ s['id'] = [struct.struct_id].pack("L").unpack("l")[0].to_s
15
+ s['nwnLibDataType'] = struct.data_type if struct.data_type
16
+ s['nwnLibDataVersion'] = struct.data_version if
17
+ struct.data_version != NWN::Gff::Struct::DEFAULT_DATA_VERSION
18
+ end
19
+
20
+ struct.sort.each {|(k,v)|
21
+ s << field_to_xml(v)
22
+ }
23
+ s
24
+ end
25
+
26
+ def field_to_xml field
27
+ e = case @format
28
+ when :nxml
29
+ XML::Node.new('field')
30
+ when :modpacker
31
+ XML::Node.new('element')
32
+ end
33
+ e['name'] = field.field_label
34
+ e['type'] = case @format
35
+ when :modpacker
36
+ NWN::Gff::Types.index(field.field_type).to_s
37
+ when :nxml
38
+ field.field_type.to_s
39
+ end
40
+
41
+ vv = case field.field_type
42
+ when :cexolocstr
43
+ case @format
44
+ when :modpacker
45
+ e['value'] = [field.str_ref].pack("L").unpack("l")[0].to_s
46
+ when :nxml
47
+ e['strRef'] = field.str_ref.to_s if
48
+ field.str_ref != NWN::Gff::Cexolocstr::DEFAULT_STR_REF
49
+ end
50
+
51
+ field.field_value.each {|lid, tx|
52
+ e << se = XML::Node.new("localString")
53
+ se['languageId'] = lid.to_s
54
+ se['value'] = NWN.iconv_gff_to_native(tx)
55
+ }
56
+
57
+ when :cexostr
58
+ e['value'] = NWN.iconv_gff_to_native(field.field_value)
59
+
60
+ when :struct
61
+ e << struct_to_xml(field.field_value)
62
+
63
+ when :list
64
+ field.field_value.each {|ee|
65
+ e << struct_to_xml(ee)
66
+ }
67
+
68
+ else
69
+ e['value'] = field.field_value.to_s
70
+ end
71
+
72
+ e
73
+ end
74
+
75
+ def xml_to_struct e, parent_data_version = nil
76
+ case @format
77
+ when :nxml
78
+ struct_id = e['id'] || raise("No struct id for: #{e.path}")
79
+ struct_id = struct_id.to_i
80
+ data_type = e['dataType']
81
+ parent_data_version ||= NWN::Gff::Struct::DEFAULT_DATA_VERSION
82
+ data_version = e['dataVersion'] || parent_data_version
83
+ when :modpacker
84
+ struct_id = [e['id'].to_i].pack("l").unpack("L")[0]
85
+ data_type = e['nwnLibDataType']
86
+ data_version = e['nwnLibDataVersion'] || parent_data_version
87
+ end
88
+
89
+ st = NWN::Gff::Struct.new(struct_id, data_type, data_version)
90
+ e.each_element {|f|
91
+ xml_to_field(f, st, parent_data_version) if
92
+ f.name == case @format
93
+ when :modpacker ; 'element'
94
+ when :nxml ; 'field'
95
+ end
96
+ }
97
+ st
98
+ end
99
+
100
+ def xml_to_field field, struct, parent_data_version
101
+ name = field['name'] || raise("No name for field: #{field.path}")
102
+ type = case @format
103
+ when :nxml
104
+ field['type']
105
+ when :modpacker
106
+ NWN::Gff::Types[field['type'].to_i]
107
+ end || raise("No type for field: #{field.path}")
108
+ v = field['value']
109
+
110
+ f = struct.add_field(name, type,
111
+ case type.to_sym
112
+ when :cexostr
113
+ NWN.iconv_native_to_gff(v)
114
+ when :cexolocstr
115
+ Hash[field.children.reject {|x| x.node_type != XML::Node::ELEMENT_NODE }.map {|ee|
116
+ [ee['languageId'].to_i, NWN.iconv_native_to_gff(ee['value'] || '')]
117
+ }]
118
+ when :list
119
+ field.children.reject {|x| x.node_type != XML::Node::ELEMENT_NODE }.map {|ee|
120
+ xml_to_struct(ee, parent_data_version)
121
+ }
122
+ when :struct
123
+ xml_to_struct(field.children.select {|x|
124
+ x.node_type == XML::Node::ELEMENT_NODE
125
+ }[0], parent_data_version)
126
+ when :byte, :char, :word, :short, :dword, :int,
127
+ :dword64, :int64
128
+ v.to_i
129
+ when :float, :double
130
+ v.to_f
131
+ when :void, :resref
132
+ v
133
+ else
134
+ raise ArgumentError, "Invalid field type #{type.inspect}. Bug."
135
+ end
136
+ )
137
+
138
+ f.str_ref = case @format
139
+ when :nxml
140
+ field['strRef'] || NWN::Gff::Cexolocstr::DEFAULT_STR_REF
141
+ when :modpacker
142
+ [v.to_i].pack("l").unpack("L")[0]
143
+ end if f.is_a?(NWN::Gff::Cexolocstr)
144
+
145
+ f
146
+ end
147
+
148
+ public
149
+
150
+ def initialize fmt
151
+ @format = fmt
152
+ end
153
+
154
+ def load io
155
+ old_encoding = NWN.setting(:out_encoding, 'UTF-8')
156
+ NWN.log_debug("Ignoring custom out_encoding for xml output, always UTF-8") if
157
+ old_encoding != 'UTF-8'
158
+
159
+ doc = XML::Parser.io(io)
160
+ root = doc.parse.root
161
+ ret = case @format
162
+ when :nxml
163
+ xml_to_struct(root)
164
+ when :modpacker
165
+ struct = root.children.select {|x| x.node_type == XML::Node::ELEMENT_NODE && x.name == 'struct' }[0]
166
+ xml_to_struct(struct, root['version'])
167
+ else
168
+ raise ArgumentError, "Unsupported XML format registered: #{@format.inspect}"
169
+ end
170
+
171
+ NWN.setting(:out_encoding, old_encoding)
172
+ ret
173
+ end
174
+
175
+ def dump data, io
176
+ old_encoding = NWN.setting(:out_encoding, 'UTF-8')
177
+ NWN.log_debug("Ignoring custom out_encoding for xml output, always UTF-8") if
178
+ old_encoding != 'UTF-8'
179
+
180
+ doc = XML::Document.new
181
+ doc.root = case @format
182
+ when :nxml
183
+ struct_to_xml(data)
184
+ when :modpacker
185
+ nd = XML::Node.new('gff')
186
+ nd['type'] = [data.data_type].pack("A4")
187
+ nd['version'] = [data.data_version].pack("A4")
188
+ nd << struct_to_xml(data)
189
+ nd
190
+ else
191
+ raise ArgumentError, "Unsupported XML format registered: #{@format.inspect}"
192
+ end
193
+ t = doc.to_s
194
+ io.write(t)
195
+
196
+ NWN.setting(:out_encoding, old_encoding)
197
+ t.size
198
+ end
199
+ end
200
+
201
+ NWN::Gff::Handler.register :nxml, /^nxml$/, NWN::Gff::Handler::XML.new(:nxml)
202
+ NWN::Gff::Handler.register :modpacker, nil, NWN::Gff::Handler::XML.new(:modpacker)
@@ -1,7 +1,7 @@
1
1
  # This file contains all YAML-specific loading and dumping code.
2
2
  require 'yaml'
3
3
 
4
- class NWN::Gff::YAML
4
+ module NWN::Gff::Handler::YAML
5
5
  # These field types can never be inlined in YAML.
6
6
  NonInlineableFields = [:struct, :list, :cexolocstr]
7
7
 
@@ -12,7 +12,9 @@ class NWN::Gff::YAML
12
12
  YAML.load(io)
13
13
  end
14
14
  def self.dump data, io
15
- io.puts data.to_yaml
15
+ d = data.to_yaml
16
+ io.puts d
17
+ d.size
16
18
  end
17
19
  end
18
20
 
@@ -46,7 +48,7 @@ end
46
48
 
47
49
  module NWN::Gff::Struct
48
50
  def to_yaml_type
49
- "!#{NWN::Gff::YAML::Domain}/struct"
51
+ "!#{NWN::Gff::Handler::YAML::Domain}/struct"
50
52
  end
51
53
 
52
54
  def to_yaml(opts = {})
@@ -55,7 +57,7 @@ module NWN::Gff::Struct
55
57
  # Inline certain structs that are small enough.
56
58
  map.style = :inline if self.size <= 1 &&
57
59
  self.values.select {|x|
58
- NWN::Gff::YAML::NonInlineableFields.index(x['type'])
60
+ NWN::Gff::Handler::YAML::NonInlineableFields.index(x['type'])
59
61
  }.size == 0
60
62
 
61
63
  map.add('__' + 'data_type', @data_type) if @data_type
@@ -75,7 +77,7 @@ module NWN::Gff::Field
75
77
  def to_yaml(opts = {})
76
78
  YAML::quick_emit(nil, opts) do |out|
77
79
  out.map(taguri, to_yaml_style) do |map|
78
- map.style = :inline unless NWN::Gff::YAML::NonInlineableFields.index(self['type'])
80
+ map.style = :inline unless NWN::Gff::Handler::YAML::NonInlineableFields.index(self['type'])
79
81
  map.add('type', self['type'])
80
82
  map.add('str_ref', self['str_ref']) if has_str_ref?
81
83
  map.add('value', self['value'])
@@ -85,7 +87,7 @@ module NWN::Gff::Field
85
87
  end
86
88
 
87
89
  # This parses the struct and extends all fields with their proper type.
88
- YAML.add_domain_type(NWN::Gff::YAML::Domain,'struct') {|t,hash|
90
+ YAML.add_domain_type(NWN::Gff::Handler::YAML::Domain,'struct') {|t,hash|
89
91
  struct = {}
90
92
  struct.extend(NWN::Gff::Struct)
91
93
 
@@ -116,4 +118,4 @@ YAML.add_domain_type(NWN::Gff::YAML::Domain,'struct') {|t,hash|
116
118
  struct
117
119
  }
118
120
 
119
- NWN::Gff.register_format_handler :yaml, /^(y|yml|yaml)$/, NWN::Gff::YAML
121
+ NWN::Gff::Handler.register :yaml, /^(y|yml|yaml)$/, NWN::Gff::Handler::YAML
@@ -18,6 +18,8 @@ describe "Gff.read/write API" do
18
18
  {
19
19
  :gff => %w{utc utd ute uti utm utp uts utt utw git are gic mod ifo fac ssf dlg itp bic},
20
20
  :yaml => %w{yml yaml},
21
+ :json => %w{json},
22
+ :nxml => %w{nxml},
21
23
  :kivinen => %w{k kivinen},
22
24
  :marshal => %w{marshal}
23
25
  }.each {|expect, arr|
@@ -46,21 +48,28 @@ describe "Gff::*" do
46
48
  t2.should == t
47
49
  end
48
50
 
49
- it "writes to io and returns the number of written bytes" do
50
- t = Gff::Reader.read(StringIO.new WELLFORMED_GFF)
51
- out = StringIO.new
52
- v = Gff::Writer.dump(t, out)
53
- v.should == out.size
54
- out.seek(0)
55
- v = out.read(v)
56
- t2 = wellformed_verify v
57
- t2.should == t
51
+ NWN::Gff::OutputFormats.keys.each do |fmt|
52
+ it "#{fmt} writes to io and returns the number of written bytes" do
53
+ t = Gff::Reader.read(StringIO.new WELLFORMED_GFF)
54
+ out = StringIO.new
55
+ v = Gff.write(out, fmt, t)
56
+ v.should == out.pos
57
+ end
58
58
  end
59
59
 
60
- it "fails on not enough data" do
61
- proc {
62
- wellformed_verify WELLFORMED_GFF[0 .. -2]
63
- }.should raise_error IOError
60
+ (NWN::Gff::OutputFormats.keys & NWN::Gff::InputFormats.keys).each do |fmt|
61
+ it "#{fmt} fails on not enough data" do
62
+ proc {
63
+ gff = Gff::Reader.read(StringIO.new WELLFORMED_GFF)
64
+ out = StringIO.new
65
+ Gff.write(out, fmt, gff)
66
+ size = out.pos
67
+ out.seek(0)
68
+ out.truncate(size - 20)
69
+ Gff.read(out, fmt)
70
+ }.should raise_error
71
+ end
72
+
64
73
  end
65
74
 
66
75
  end
@@ -8,7 +8,7 @@ describe "Kivinen Support" do
8
8
 
9
9
  expected = KIVINEN_EXPECT.dup
10
10
 
11
- NWN::Gff::Kivinen.format(g, true) do |label, entry|
11
+ NWN::Gff::Handler::Kivinen.format(g, true) do |label, entry|
12
12
  w = expected.shift
13
13
  label.should == w[0]
14
14
  case entry
@@ -10,6 +10,8 @@ unless Object.const_defined?('NWN')
10
10
  include NWN
11
11
  end
12
12
 
13
+ $options = {}
14
+
13
15
  NWN.setting(:debug, 0)
14
16
 
15
17
  GffFieldValidations = {
@@ -0,0 +1,82 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "Gff::Struct" do
4
+ ReadWriteFormats = (NWN::Gff::OutputFormats.keys & NWN::Gff::InputFormats.keys)
5
+ NoRootNoMetadata = [:gff]
6
+
7
+ before(:each) do
8
+ @manual = Gff::Struct.new(0x0, 'root', 'V3.2') do |s|
9
+ s.add_struct 'a', Gff::Struct.new(0x1) do |a|
10
+ a.v.add_struct 'b', Gff::Struct.new(0x02) do |b|
11
+ b.v.add_field 'hi', :int, 1
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ def read_write what, format
18
+ out = StringIO.new
19
+ Gff.write(out, format, what)
20
+ out.seek(0)
21
+ n = Gff.read(out, format)
22
+ end
23
+
24
+ it "has the proper data structure when building inline" do
25
+ @manual.data_type.should == 'root'
26
+ @manual.data_version.should == 'V3.2'
27
+ (@manual / 'a').path.should == '/a'
28
+ (@manual / 'a').v.path.should == '/a'
29
+ (@manual / 'a$').path.should == '/a'
30
+ (@manual / 'a/b$').path.should == '/a/b'
31
+ (@manual / 'a/b/hi').path.should == '/a/b/hi'
32
+ (@manual / 'a/b/hi$').should == 1
33
+ end
34
+
35
+ describe "#data_type" do
36
+ ReadWriteFormats.each do |format|
37
+ it "#{format} keeps explicit root data types" do
38
+ @manual.data_type = 'EXPL'
39
+ n = read_write(@manual, format)
40
+ n.data_type.should == 'EXPL'
41
+ end
42
+ end
43
+
44
+ (ReadWriteFormats - NoRootNoMetadata).each do |format|
45
+ it "#{format} keeps explicit non-root data types" do
46
+ (@manual / 'a/b$').data_type = 'EXPL'
47
+ n = read_write(@manual, format)
48
+ (n / 'a/b$').data_type.should == 'EXPL'
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#data_version" do
54
+ ReadWriteFormats.each do |format|
55
+ it "#{format} keeps explicit root data version" do
56
+ @manual.data_version = 'EXPL'
57
+ n = read_write(@manual, format)
58
+ n.data_version.should == 'EXPL'
59
+ end
60
+ end
61
+
62
+ (ReadWriteFormats - NoRootNoMetadata).each do |format|
63
+ it "#{format} keeps explicit non-root data version" do
64
+ (@manual / 'a/b$').data_version = 'EXPL'
65
+ n = read_write(@manual, format)
66
+ (n / 'a/b$').data_version.should == 'EXPL'
67
+ end
68
+ end
69
+
70
+ ReadWriteFormats.each do |format|
71
+ it "#{format} reads implicit data_version as DEFAULT_DATA_VERSION" do
72
+ n = read_write(@manual, format)
73
+
74
+ (@manual / 'a/b$').data_version.should == NWN::Gff::Struct::DEFAULT_DATA_VERSION
75
+ @manual.data_version.should == NWN::Gff::Struct::DEFAULT_DATA_VERSION
76
+ (n / 'a/b$').data_version.should == NWN::Gff::Struct::DEFAULT_DATA_VERSION
77
+ n.data_version.should == NWN::Gff::Struct::DEFAULT_DATA_VERSION
78
+ end
79
+ end
80
+ end
81
+ end
82
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nwn-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.11
4
+ version: 0.4.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernhard Stoeckner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-20 00:00:00 +01:00
12
+ date: 2010-01-15 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -36,54 +36,55 @@ files:
36
36
  - CHANGELOG
37
37
  - README
38
38
  - Rakefile
39
- - bin/nwn-gff
40
39
  - bin/nwn-dsl
41
- - bin/nwn-erf
40
+ - bin/nwn-gff
42
41
  - bin/nwn-irb
43
- - spec/bin_dsl_spec.rb
42
+ - bin/nwn-erf
43
+ - spec/kivinen_expect.rb
44
+ - spec/wellformed_gff.bic
44
45
  - spec/gff_spec.rb
46
+ - spec/struct_data_type_version_spec.rb
47
+ - spec/res_spec.rb
48
+ - spec/erf_spec.rb
45
49
  - spec/key_spec.rb
46
- - spec/bin_gff_spec.rb
47
- - spec/bin_erf_spec.rb
48
- - spec/kivinen_expect.rb
50
+ - spec/kivinen_spec.rb
49
51
  - spec/field_spec.rb
50
52
  - spec/cexolocstr_spec.rb
51
- - spec/kivinen_spec.rb
52
- - spec/erf_spec.rb
53
53
  - spec/struct_spec.rb
54
- - spec/res_spec.rb
54
+ - spec/spec_helper.rb
55
55
  - spec/tlk_spec.rb
56
56
  - spec/json_spec.rb
57
+ - spec/bin_dsl_spec.rb
58
+ - spec/bin_gff_spec.rb
59
+ - spec/bin_erf_spec.rb
57
60
  - spec/twoda_spec.rb
58
- - spec/spec_helper.rb
59
- - spec/data_type_spec.rb
60
- - spec/wellformed_gff.bic
61
- - lib/nwn/erf.rb
62
- - lib/nwn/json_support.rb
63
- - lib/nwn/io.rb
64
- - lib/nwn/settings.rb
61
+ - lib/nwn/scripting.rb
65
62
  - lib/nwn/key.rb
66
- - lib/nwn/all.rb
67
- - lib/nwn/res.rb
68
63
  - lib/nwn/twoda.rb
69
- - lib/nwn/tlk.rb
64
+ - lib/nwn/json_support.rb
65
+ - lib/nwn/erf.rb
66
+ - lib/nwn/kivinen_support.rb
67
+ - lib/nwn/settings.rb
70
68
  - lib/nwn/gff.rb
71
69
  - lib/nwn/yaml_support.rb
72
- - lib/nwn/scripting.rb
73
- - lib/nwn/kivinen_support.rb
74
- - lib/nwn/gff/writer.rb
75
- - lib/nwn/gff/struct.rb
76
- - lib/nwn/gff/list.rb
70
+ - lib/nwn/xml_support.rb
71
+ - lib/nwn/tlk.rb
72
+ - lib/nwn/io.rb
73
+ - lib/nwn/res.rb
74
+ - lib/nwn/all.rb
75
+ - lib/nwn/gff/field.rb
77
76
  - lib/nwn/gff/reader.rb
78
77
  - lib/nwn/gff/cexolocstr.rb
79
- - lib/nwn/gff/field.rb
78
+ - lib/nwn/gff/struct.rb
79
+ - lib/nwn/gff/list.rb
80
+ - lib/nwn/gff/writer.rb
80
81
  - tools/verify.sh
81
82
  - tools/migrate_03x_to_04x.sh
82
83
  - scripts/truncate_floats.rb
84
+ - scripts/reformat_2da
83
85
  - scripts/clean_locstrs.rb
84
- - scripts/debug_check_objid.rb
85
86
  - scripts/extract_all_items.rb
86
- - scripts/reformat_2da
87
+ - scripts/debug_check_objid.rb
87
88
  - BINARIES
88
89
  - HOWTO
89
90
  - SCRIPTING
@@ -1,128 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'spec_helper')
2
-
3
- describe "Gff::Struct#data_type" do
4
-
5
- before(:each) do
6
- @manual = Gff::Struct.new(0x0, 'root', 'V3.1') do |s|
7
- s.add_struct 'a', Gff::Struct.new(0x1) do |a|
8
- a.v.add_struct 'b', Gff::Struct.new(0x02) do |b|
9
- b.v.add_field 'hi', :int, 1
10
- end
11
- end
12
- end
13
- end
14
-
15
- it "has the proper data types when building inline" do
16
- verify @manual
17
- end
18
-
19
- unless Gff::InputFormats[:json] && Gff::OutputFormats[:json]
20
- $stderr.puts "Partial or no json support, not running json specs!"
21
- else
22
-
23
- it "keeps explicit data types for json" do
24
- @manual['a'].v['b'].v.data_type = 'EXPLICIT'
25
- json = StringIO.new
26
- Gff.write(json, :json, @manual)
27
- json.seek(0)
28
- n = Gff.read(json, :json)
29
- n['a'].v.path.should == '/a'
30
- n['a'].v.data_type.should == nil
31
- n['a'].v['b'].v.data_type.should == 'EXPLICIT'
32
- end
33
-
34
- it "has the proper data type when reading from json" do
35
- struct = <<EOS
36
- {
37
- "a": {
38
- "type": "struct",
39
- "value": {
40
- "b": {
41
- "type": "struct",
42
- "value": {
43
- "hi": {
44
- "type": "int",
45
- "value": 1
46
- },
47
- "__struct_id": 2
48
- }
49
- },
50
- "__struct_id": 1
51
- }
52
- },
53
- "__data_version": "V3.1",
54
- "__data_type": "root",
55
- "__struct_id": 0
56
- }
57
- EOS
58
-
59
- struct = Gff.read(StringIO.new(struct), :json)
60
-
61
- verify struct
62
- end
63
-
64
- end
65
-
66
- it "keeps explicit data types for yaml" do
67
- (@manual / 'a/b').v.data_type = 'EXPLICIT'
68
- json = StringIO.new
69
- Gff.write(json, :yaml, @manual)
70
- json.seek(0)
71
- n = Gff.read(json, :yaml)
72
- (n / 'a/b/hi').path.should == "/a/b/hi"
73
- (n / 'a$').data_type.should == nil
74
- (n / 'a/b$').data_type.should == "EXPLICIT"
75
- end
76
-
77
- it "has the proper data type when reading from yaml" do
78
- struct = <<EOS
79
- --- !nwn-lib.elv.es,2008-12/struct
80
- __data_type: root
81
- __data_version: V3.1
82
- __struct_id: 0
83
- a:
84
- type: :struct
85
- value: !nwn-lib.elv.es,2008-12/struct
86
- __data_type: ATYPE
87
- __struct_id: 1
88
- b:
89
- type: :struct
90
- value: !nwn-lib.elv.es,2008-12/struct {__data_type: BTYPE, __struct_id: 2, hi: {type: :int, value: 1}}
91
- EOS
92
- struct = Gff.read(StringIO.new(struct), :yaml)
93
-
94
- verify struct
95
- end
96
-
97
- it "has the proper data type when reading from yaml with overriden data_type" do
98
- struct = <<EOS
99
- --- !nwn-lib.elv.es,2008-12/struct
100
- __data_type: root
101
- __data_version: V3.1
102
- __struct_id: 0
103
- a:
104
- type: :struct
105
- value: !nwn-lib.elv.es,2008-12/struct
106
- __data_type: DTYPE
107
- __struct_id: 1
108
- b:
109
- type: :struct
110
- value: !nwn-lib.elv.es,2008-12/struct {__data_type: BTYPE, __struct_id: 2, hi: {type: :int, value: 1}}
111
- EOS
112
- struct = Gff.read(StringIO.new(struct), :yaml)
113
-
114
- (struct / 'a$').data_type.should == 'DTYPE'
115
- (struct / 'a$').path.should == '/a'
116
- end
117
-
118
- def verify struct
119
- struct.data_type.should == 'root'
120
- struct.data_version.should == 'V3.1'
121
- (struct / 'a').path.should == '/a'
122
- (struct / 'a').v.path.should == '/a'
123
- (struct / 'a$').path.should == '/a'
124
- (struct / 'a/b$').path.should == '/a/b'
125
- (struct / 'a/b/hi').path.should == '/a/b/hi'
126
- (struct / 'a/b/hi$').should == 1
127
- end
128
- end