ifc4d: tolerate activities without a CalendarObjectId in P6 import (#5617)

Importing a Primavera P6 XML crashed with
`AttributeError: 'NoneType' object has no attribute 'text'` in
P62Ifc.parse_activity_xml, which read
activity.find("pr:CalendarObjectId").text unconditionally. CalendarObjectId
is optional on a P6 Activity; when omitted, the activity inherits the
project's ActivityDefaultCalendarObjectId.

Capture the project default in parse_xml and fall back to it when an
activity has no CalendarObjectId (`calendar_id or self.default_calendar_id`).

Verified on the reporter's attached file (20241021 Cronograma.xml): 3 of 14
activities lack a CalendarObjectId and reproduced the exact crash on
v0.8.0; after the fix parse_xml completes and those activities resolve to
the project default calendar "2" (a valid calendar in the file). An
activity with an explicit CalendarObjectId keeps its own value.

Fixes the P6 re-import crash reported in #5617 (that issue tracks several
Gantt items; this addresses the import AttributeError).

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 694a44e638)
This commit is contained in:
Petru Conduraru
2026-07-12 19:01:22 +03:00
committed by Dion Moult
parent c4609a634c
commit 8d9f027f3e
+6 -1
View File
@@ -28,6 +28,7 @@ class P62Ifc:
self.file = None
self.work_plan = None
self.project = {}
self.default_calendar_id = None
self.calendars = {}
self.wbs = {}
self.root_activites = []
@@ -89,6 +90,7 @@ class P62Ifc:
self.ns = {"pr": root.tag[1:].partition("}")[0]}
project = root.find("pr:Project", self.ns)
self.project["Name"] = project.findtext("pr:Name") or "Unnamed"
self.default_calendar_id = project.findtext("pr:ActivityDefaultCalendarObjectId", namespaces=self.ns)
self.parse_calendar_xml(root)
self.parse_calendar_xml(project)
self.parse_wbs_xml(project)
@@ -174,6 +176,9 @@ class P62Ifc:
self.wbs[wbs_id]["activities"].append(activity_id)
else:
self.root_activites.append(activity_id)
# CalendarObjectId is optional in the P6 schema: an activity without one
# inherits the project's ActivityDefaultCalendarObjectId.
calendar_id = activity.findtext("pr:CalendarObjectId", namespaces=self.ns)
self.activities[activity_id] = {
"Name": activity.find("pr:Name", self.ns).text,
"Identification": activity.find("pr:Id", self.ns).text,
@@ -181,7 +186,7 @@ class P62Ifc:
"FinishDate": datetime.datetime.fromisoformat(activity.find("pr:FinishDate", self.ns).text),
"PlannedDuration": activity.find("pr:PlannedDuration", self.ns).text,
"Status": activity.find("pr:Status", self.ns).text,
"CalendarObjectId": activity.find("pr:CalendarObjectId", self.ns).text,
"CalendarObjectId": calendar_id or self.default_calendar_id,
"ifc": None,
}