nwn-lib 0.4.11 → 0.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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