Class: MppReader::FieldMap

Inherits:
Object
  • Object
show all
Defined in:
lib/mpp_reader/field_map.rb

Overview

Maps logical fields (task name, start, …) to where their values live in an entity’s data blocks. The definition is stored in the project Props under entity-specific keys, as 28-byte entries (ported from MPXJ FieldMap.createFieldMap): mask u32@0, fixed-data offset u16@4, field type u32@12, category u16@20.

Locations: :fixed_data (offset into a FixedData record; a second fixed block is signalled by offsets restarting), :var_data (key into Var2Data; for MPP14 the key is the low word of the field type), :meta_data (bit mask over a FixedMeta record).

Defined Under Namespace

Classes: Item

Constant Summary collapse

FIXED_DATA_SIZES =

Bytes each data type occupies in a fixed-data record (MPXJ FieldMap#getFixedDataFieldSize); used to size-check records.

Hash.new(0).update(
  date: 4, integer: 4, duration: 4,
  time_units: 2, constraint: 2, priority: 2, percentage: 2, task_type: 2,
  accrue: 2, short: 2, boolean: 2, delay: 2, workgroup: 2, rate_units: 2,
  earned_value_method: 2, resource_request_type: 2,
  currency: 8, units: 8, rate: 8, work: 8,
  work_units: 1, guid: 16
).freeze
ENTITIES =
{
  task: [FieldTables::TASK_FIELD_BASE, FieldTables::TASK_FIELDS,
         FieldTables::TASK_FIELD_TYPES, [131_092, 50_331_668]],
  resource: [FieldTables::RESOURCE_FIELD_BASE, FieldTables::RESOURCE_FIELDS,
             FieldTables::RESOURCE_FIELD_TYPES, [131_093, 50_331_669]],
  assignment: [FieldTables::ASSIGNMENT_FIELD_BASE, FieldTables::ASSIGNMENT_FIELDS,
               FieldTables::ASSIGNMENT_FIELD_TYPES, [131_095, 50_331_671]]
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, entity:) ⇒ FieldMap

Returns a new instance of FieldMap.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/mpp_reader/field_map.rb', line 52

def initialize(data, entity:)
  base, fields, types, = ENTITIES.fetch(entity)
  @map = {}
  @max_fixed_data_size = Hash.new(0)
  last_offset = 0
  data_block = 0
  pos = 0

  while pos + 28 <= data.bytesize
    mask = data.byteslice(pos, 4).unpack1("V")
    fixed_offset = data.byteslice(pos + 4, 2).unpack1("v")
    type_value = data.byteslice(pos + 12, 4).unpack1("V")
    category = data.byteslice(pos + 20, 2).unpack1("v")
    pos += 28

    next unless (type_value & 0xFFFF0000) == base

    field = fields[type_value & 0xFFFF]
    next if field.nil?

    # NOTE: MPXJ substitutes var data keys for some custom fields
    # (FieldMap14.VAR_DATA_MAP); not implemented yet.
    var_key = type_value & 0xFFFF

    item =
      case category
      when 0x0B then Item.new(:meta_data, 0, nil, nil, mask, 0)
      when 0x64 then Item.new(:meta_data, 0, nil, nil, mask, 1)
      else
        if fixed_offset != 0xFFFF
          data_block += 1 if fixed_offset < last_offset
          last_offset = fixed_offset
          Item.new(:fixed_data, data_block, fixed_offset, nil, mask, 0)
        elsif var_key != 0
          Item.new(:var_data, 0, nil, var_key, mask, 0)
        end
      end
    next if item.nil?

    item.data_type, item.units_field = types[field]
    if item.location == :fixed_data
      field_end = item.fixed_offset + FIXED_DATA_SIZES[item.data_type]
      if field_end > @max_fixed_data_size[item.data_block]
        @max_fixed_data_size[item.data_block] = field_end
      end
    end
    @map[field] = item
  end
end

Class Method Details

.for_assignments(props) ⇒ Object



40
# File 'lib/mpp_reader/field_map.rb', line 40

def self.for_assignments(props) = from_props(props, :assignment)

.for_resources(props) ⇒ Object



39
# File 'lib/mpp_reader/field_map.rb', line 39

def self.for_resources(props) = from_props(props, :resource)

.for_tasks(props) ⇒ Object



38
# File 'lib/mpp_reader/field_map.rb', line 38

def self.for_tasks(props) = from_props(props, :task)

.from_props(props, entity) ⇒ Object



42
43
44
45
46
47
48
49
50
# File 'lib/mpp_reader/field_map.rb', line 42

def self.from_props(props, entity)
  data = ENTITIES.fetch(entity)[3].filter_map { |key| props[key] }.first
  if data.nil?
    raise UnsupportedFormatError,
          "no #{entity} field map in project Props - default field maps are not implemented"
  end

  new(data, entity: entity)
end

Instance Method Details

#[](field) ⇒ Object



102
# File 'lib/mpp_reader/field_map.rb', line 102

def [](field) = @map[field]

#max_fixed_data_size(block) ⇒ Object



104
# File 'lib/mpp_reader/field_map.rb', line 104

def max_fixed_data_size(block) = @max_fixed_data_size[block]