Read Microsoft Project, Primavera and other schedule files from Elixir, powered by MPXJ.
{:ok, project} = MpxjEx.read("plan.mpp")
project.properties.project_title
#=> "Office move"
for task <- project.tasks do
{task.name, task.start, task.finish, task.percent_complete}
endMpxjEx runs MPXJ in a JVM, has it write the schedule as JSON, and turns that
into Elixir structs: tasks, resources, assignments, dependencies and
calendars, with dates as NaiveDateTimes and durations as numbers in the
unit you choose.
Supported formats
The format is detected from the file content. MPXJ reads MPP (Project 98 onwards), MPX, MSPDI (Project XML), MPD, Primavera XER, PMXML, P3 and SureTrak, Asta Powerproject and Easyplan, Phoenix, FastTrack, GanttProject, Planner, TurboProject, ConceptDraw, Synchro, Gantt Designer, SDEF, Sage, Project Commander, Deltek Open Plan and Edraw Project files.
MpxjEx.convert/3 writes MSPDI, MPX, XER, PMXML, Planner, SDEF and JSON.
Installation
Add mpxj_ex to your dependencies:
def deps do
[
{:mpxj_ex, "~> 0.1"}
]
endYou need a Java runtime, version 8 or newer, on the PATH or in
JAVA_HOME. MPXJ itself (about 30 MB of jars) is downloaded the first time
it's needed; see MpxjEx.Installer to choose the version or location. For
releases and Docker images, install it at build time instead:
# config/prod.exs
config :mpxj_ex, auto_install: false, jar_dir: "/app/mpxj"mix mpxj_ex.install --path /app/mpxj
Usage
Reading
{:ok, project} = MpxjEx.read("plan.mpp", time_units: :hours)
task = MpxjEx.Project.get_task_by_id(project, 3)
task.duration #=> 16.0
task.fields.baseline_cost # every attribute MPXJ read is in :fields
MpxjEx.Project.predecessors(project, task)
MpxjEx.Project.task_resources(project, task)
MpxjEx.Project.task_tree(project)Files holding several projects (Primavera XER and PMXML) can be read with
MpxjEx.read_all/2.
Converting
:ok = MpxjEx.convert("plan.mpp", "plan.xml") # MS Project XML
:ok = MpxjEx.convert("plan.mpp", "plan.xer") # Primavera XER
:ok = MpxjEx.convert("plan.mpp", "p6.xml", format: :pmxml)Many files: a long running JVM
Starting a JVM takes about half a second per call. To read many files, run
an MpxjEx.Server and the JVM stays up between calls (a file then takes tens
of milliseconds) and handles several at once:
children = [
{MpxjEx.Server, name: MpxjEx.Server, pool_size: 4, max_memory: "2g"}
]
# then either pass it on each call...
MpxjEx.read("plan.mpp", server: MpxjEx.Server)
# ...or make it the default
config :mpxj_ex, server: MpxjEx.ServerErrors
Failures return {:error, %MpxjEx.Error{reason: reason}}, where reason is
for example :file_not_found, :unsupported_format, :password_protected
or :java_not_found. The ! variants raise it.
Deploying to production
In production you need a Java runtime in the image that runs your release,
MPXJ installed at build time, and an MpxjEx.Server in your supervision
tree. The deployment guide covers all of it: Docker,
sizing memory and concurrency, handling untrusted uploads, configuration and
troubleshooting.
examples/mpxj_service
is a complete example: an HTTP API with a production Dockerfile and
docker-compose.yml.
cd examples/mpxj_service
docker compose up --build -d
curl -F file=@plan.mpp http://localhost:4000/projects
Development
mix test # tests tagged :java are skipped without java
mix mpxj_ex.build_bridge # after changing java/src (needs a JDK)
The test fixtures are real files from MPXJ's own test data, see
test/fixtures/README.md.
To publish a new version, follow
RELEASING.md.
License
MpxjEx is MIT licensed. MPXJ, which it downloads and runs, is LGPL-2.1.