joniles / mpxj

Primary repository for MPXJ library
http://www.mpxj.org/
GNU Lesser General Public License v2.1
240 stars 101 forks source link

MSPDIWriter: export UDFs/CustomFields #702

Closed alex-matatov closed 1 month ago

alex-matatov commented 2 months ago

Hi Jon,

I need your help with exporting UserDefineFields/CustomFields in MSPDIWriter.

Our case: we create UserDefineField and assign it to Task. Then we would like to export UDFs to P6[xml | xer] (works) and MSPDI formats (doesn’t work)

By looking on the code I see that MSPDIWriter maps UDFs to MSP/CustomFields. Excellent!

m_userDefinedFieldMap = new UserDefinedFieldMap(projectFile, MAPPING_TARGET_CUSTOM_FIELDS);

Indeed, in the output file I see UDF definitions like that:

 <ExtendedAttributes>
        <ExtendedAttribute>
            <FieldID>188743731</FieldID>
            <!— maybe FieldName should be TEXT1..20 and <Alias>CF-A-1</Alias> should be added ? —>
            <FieldName>CF-A-1</FieldName> 
            <CFType>7</CFType>
            <UserDef>1</UserDef>
            <Alias>CF-A-1</Alias>
        </ExtendedAttribute>

If I populate lookup table like below I see OutlineCodes

var fieldValueItem = new CustomFieldValueItem(lastExtId.getAndIncrement());
fieldValueItem.setGUID(UUID.randomUUID());
fieldValueItem.setValue(fieldValue);
fieldValueItem.setType(isNumeric ? CustomFieldValueDataType.NUMBER: CustomFieldValueDataType.TEXT);
udf.getLookupTable().add(fieldValueItem);
<OutlineCodes>
        <OutlineCode>
            <FieldID>188743731</FieldID>
            <Values>
                <Value>
                    <ValueID>188744018</ValueID>
                    <FieldGUID>20D64C29-9550-4AA3-926D-2CFB074A6DC6</FieldGUID>
                    <Type>21</Type>
                    <IsCollapsed>0</IsCollapsed>
                    <Value>txt-50</Value>
                </Value>
            </Values>

By the way, maybe MPXJ could run “add to lookup table” code above when value is assigned to Task ? -> externalTask.set(udf.getFieldType(), fieldValue);

However, the writer does not add Task/ExtendedAttribute (assign custom code) because it expects to see TaskField instead of UserDefinedField

private void writeTaskExtendedAttribute(List<Project.Tasks.Task.ExtendedAttribute> extendedAttributes, Task mpx, FieldType mpxFieldID)
{
   Object value = mpx.getCachedValue(mpxFieldID);

   if (FieldTypeHelper.valueIsNotDefault(mpxFieldID, value))
   {
      FieldType mappedFieldType = m_userDefinedFieldMap.getTarget(mpxFieldID);
      if (mappedFieldType instanceof TaskField) // do we need this check here ? Why UserDefineField can't be there ?
      {
         Project.Tasks.Task.ExtendedAttribute attrib = m_factory.createProjectTasksTaskExtendedAttribute();
         extendedAttributes.add(attrib);
         attrib.setFieldID(Integer.toString(FieldTypeHelper.getFieldID(mappedFieldType)));
         attrib.setValue(DatatypeConverter.printCustomField(this, value, mappedFieldType.getDataType()));
         attrib.setDurationFormat(printCustomFieldDurationFormat(value));
         setValueGUID(attrib, mappedFieldType);
      }
   }
}
<!-- It is missed in output file -->
<Task>
           <ExtendedAttribute>
                <FieldID> 188744018</FieldID>
                <Value>txt-50</Value>
                <ValueGUID>40DC2844-3D02-EF11-842E-06D36BFBCFD9</ValueGUID>
            </ExtendedAttribute>

So, my question is - how can I export UDFs as MSP/Custom codes ?

joniles commented 2 months ago

Interesting!

I tried a couple of things to verify what MPXJ is doing. The first was to try a straightforward conversion from XER to MSPDI. I used this XER file (sorry I had to ad the .txt extension to get GitHub to accept it as an attachment) test.xer.txt

Which I then ran through the MpxjConvert sample to produce an MSPDI file: test.xml.txt

I'm seeing these field definitions at the head of the MSPDI file:

<ExtendedAttributes>
    <ExtendedAttribute>
        <FieldID>188743731</FieldID>
        <FieldName>Text1</FieldName>
        <Alias>UDF 1</Alias>
    </ExtendedAttribute>
    <ExtendedAttribute>
        <FieldID>188743767</FieldID>
        <FieldName>Number1</FieldName>
        <Alias>UDF 2</Alias>
    </ExtendedAttribute>
</ExtendedAttributes>

Then on each task I'm seeing assignments like this:

<ExtendedAttribute>
    <FieldID>188743731</FieldID>
    <Value>Text Value 3</Value>
</ExtendedAttribute>
<ExtendedAttribute>
    <FieldID>188743767</FieldID>
    <Value>3</Value>
</ExtendedAttribute>

Which is what I'd expect.

I then updated the MpxjCreate sample to try out the code to create and populate a UDF from scratch. I added this block of code just before the file is written:

UserDefinedField udf = new UserDefinedField(
   1,
   "External Name",
   "Internal Name",
   FieldTypeClass.TASK,
   false,
   DataType.STRING);
file.getUserDefinedFields().add(udf);

task6.set(udf, "Task 6");

Here's the definition section at the head of the resulting file:

<ExtendedAttributes>
    <ExtendedAttribute>
        <FieldID>188743734</FieldID>
        <FieldName>Text2</FieldName>
    </ExtendedAttribute>
    <ExtendedAttribute>
        <FieldID>188743731</FieldID>
        <FieldName>Text1</FieldName>
        <Alias>My Custom Field</Alias>
    </ExtendedAttribute>
</ExtendedAttributes>

Then we can see the assignment to the task:

<ExtendedAttribute>
    <FieldID>188743734</FieldID>
    <Value>Task 6</Value>
</ExtendedAttribute>

So I'm seeing pretty much what I expect - the only caveat is that the MSPDI code could be a bit smarter in that it is expecting a CustomField instance to supply the alias for the field, whereas we've only set up a UserDefinedField. The code should be able to fall back on the "external name" attribute of the UserDefinedField instance for the alias.

It would be interesting to compare this sample code to your own code to see where things diverge. For the moment I've deliberately ignored lookup tables. Typically we'd only want to use a lookup table if we wanted to constrain user input when editing the schedule in MS Project. I would agree with you that the code for handling lookup tables could be made more "developer friendly". I'd be keen to ensure that we can get your code working without lookup tables first.

alex-matatov commented 1 month ago

Hi Jon,

Thank you so much for the code! I found out the reason why UDFs were not written. It was due to "bug" in my code. Everything works as expected. I am sorry that I distracted you.

Have a good day, Alex

joniles commented 1 month ago

No problem - I'm glad you identified the issue.