# `MpxjEx`
[🔗](https://github.com/rocket4ce/mpxj_ex/blob/v0.1.0/lib/mpxj_ex.ex#L1)

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

    {:ok, project} = MpxjEx.read("plan.mpp")

    project.properties.project_title
    #=> "Office move"

    for task <- project.tasks, do: {task.name, task.start, task.finish}

`read/2` returns an `MpxjEx.Project` holding tasks, resources,
assignments, dependencies and calendars. `convert/3` converts a file to
another format.

The file format is detected from the content. MPXJ reads, among others:
MPP, MPX, MSPDI (MS Project XML), Primavera XER, PMXML, P3 and SureTrak,
Asta Powerproject, Phoenix, FastTrack, GanttProject, Planner, ConceptDraw,
Synchro, Deltek Open Plan and Edraw Project.

## Requirements

MPXJ is a Java library, so a Java runtime (8 or newer) must be installed.
MPXJ itself is downloaded on first use, see `MpxjEx.Installer`.

Each call starts a JVM, which takes around half a second. To read many
files, run an `MpxjEx.Server`.

## Options

`read/2`, `read_all/2` and `convert/3` accept:

  * `:server` - an `MpxjEx.Server` to send the request to, instead of
    starting a JVM. Defaults to `config :mpxj_ex, :server`.
  * `:timeout` - how long to wait for an `MpxjEx.Server`, in milliseconds
    (default 60_000)
  * `:max_memory` - JVM maximum heap size, such as `"2g"`, when no server is used
  * `:java` - path to the java executable
  * `:jvm_args` - extra arguments for the JVM

`read/2` and `read_all/2` also accept:

  * `:time_units` - unit for durations, work and lags: `:seconds` (default),
    `:minutes`, `:hours`, `:days`, `:weeks`, `:months` or `:years`.
    Durations are converted using the task's calendar, so with `:days`
    an 8 hour task on a standard calendar is `1.0`.
  * `:timephased` - include timephased work and cost data (default `false`)
  * `:raw` - return the JSON MPXJ produced, decoded but otherwise untouched

# `format`

```elixir
@type format() :: :mspdi | :mpx | :xer | :pmxml | :planner | :sdef | :json
```

# `time_unit`

```elixir
@type time_unit() ::
  :minutes
  | :hours
  | :days
  | :weeks
  | :months
  | :years
  | :percent
  | :elapsed_minutes
  | :elapsed_hours
  | :elapsed_days
  | :elapsed_weeks
  | :elapsed_months
  | :elapsed_years
  | :elapsed_percent
```

# `convert`

```elixir
@spec convert(Path.t(), Path.t(), keyword()) :: :ok | {:error, MpxjEx.Error.t()}
```

Converts a schedule file to another format.

The output format comes from the `:format` option, or else from the
output file extension:

| Format     | Extension  | Description                       |
| ---------- | ---------- | --------------------------------- |
| `:mspdi`   | `.xml`     | Microsoft Project XML             |
| `:mpx`     | `.mpx`     | Microsoft Project exchange format |
| `:xer`     | `.xer`     | Primavera P6 XER                  |
| `:pmxml`   | `.pmxml`   | Primavera P6 XML                  |
| `:planner` | `.planner` | GNOME Planner                     |
| `:sdef`    | `.sdef`    | Standard Data Exchange Format     |
| `:json`    | `.json`    | MPXJ's JSON                       |

MPXJ can't write MPP files.

See the module documentation for the other options.

## Examples

    :ok = MpxjEx.convert("plan.mpp", "plan.xml")
    :ok = MpxjEx.convert("plan.mpp", "plan-p6.xml", format: :pmxml)

# `convert!`

```elixir
@spec convert!(Path.t(), Path.t(), keyword()) :: :ok
```

Same as `convert/3`, but raises `MpxjEx.Error` on failure.

# `read`

```elixir
@spec read(Path.t(), keyword()) ::
  {:ok, MpxjEx.Project.t() | map()} | {:error, MpxjEx.Error.t()}
```

Reads a schedule file.

When a file holds several projects (as Primavera XER and PMXML files
can), this returns the first one; use `read_all/2` to get them all.

See the module documentation for the options.

## Examples

    {:ok, project} = MpxjEx.read("plan.mpp", time_units: :hours)
    {:error, %MpxjEx.Error{reason: :file_not_found}} = MpxjEx.read("missing.mpp")

# `read!`

```elixir
@spec read!(Path.t(), keyword()) :: MpxjEx.Project.t() | map()
```

Same as `read/2`, but raises `MpxjEx.Error` on failure.

# `read_all`

```elixir
@spec read_all(Path.t(), keyword()) ::
  {:ok, [MpxjEx.Project.t() | map()]} | {:error, MpxjEx.Error.t()}
```

Reads every project in a file.

See the module documentation for the options.

# `read_all!`

```elixir
@spec read_all!(Path.t(), keyword()) :: [MpxjEx.Project.t() | map()]
```

Same as `read_all/2`, but raises `MpxjEx.Error` on failure.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
