# MpxjEx

Read Microsoft Project, Primavera and other schedule files from Elixir,
powered by [MPXJ](https://www.mpxj.org).

```elixir
{: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}
end
```

MpxjEx 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 `NaiveDateTime`s 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:

```elixir
def deps do
  [
    {:mpxj_ex, "~> 0.1"}
  ]
end
```

You 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:

```elixir
# config/prod.exs
config :mpxj_ex, auto_install: false, jar_dir: "/app/mpxj"
```

```sh
mix mpxj_ex.install --path /app/mpxj
```

## Usage

### Reading

```elixir
{: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

```elixir
: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:

```elixir
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.Server
```

### Errors

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](guides/deployment.md) covers all of it: Docker,
sizing memory and concurrency, handling untrusted uploads, configuration and
troubleshooting.

[`examples/mpxj_service`](https://github.com/rocket4ce/mpxj_ex/tree/main/examples/mpxj_service)
is a complete example: an HTTP API with a production `Dockerfile` and
`docker-compose.yml`.

```sh
cd examples/mpxj_service
docker compose up --build -d
curl -F file=@plan.mpp http://localhost:4000/projects
```

## Development

```sh
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`](https://github.com/rocket4ce/mpxj_ex/blob/main/test/fixtures/README.md).
To publish a new version, follow
[`RELEASING.md`](https://github.com/rocket4ce/mpxj_ex/blob/main/RELEASING.md).

## License

MpxjEx is MIT licensed. MPXJ, which it downloads and runs, is
[LGPL-2.1](https://github.com/joniles/mpxj/blob/master/LICENSE).
