# `MishkaCmsMigration.Sources.WordPress.Export`
[🔗](https://github.com/mishka-group/mishka_cms_migration/blob/v0.0.1/lib/mishka_cms_migration/sources/__wordpress/export.ex#L5)

A WordPress export file (WXR, what Tools ▸ Export makes) read as it streams by
(`MishkaCmsMigration.Sources.WordPressFile`): `fold/3` hands each child of its `<channel>`, one at
a time, to a function, and never holds more of the file than the child it is reading.

A child is an `t:element/0`: its own text, its attributes and its children by name, each a list
in the order the file gives them. `text/1`, `text/2`, `all/2` and `attribute/2` read it. A
`<channel>`'s children are the site's own fields (`title`, `link`, `wp:wxr_version`, …), its
authors (`wp:author`), categories (`wp:category`), tags (`wp:tag`) and other terms (`wp:term`),
and its items (`item`): every post, page, file, reusable block and menu item, each with its
`category` references, its `wp:postmeta` and its `wp:comment`s.

The file is read with `:xmerl_sax_parser` from the disk in blocks. An export never declares a
document type, so a file that does is refused before anything in it is read: no external entity,
DTD or other address it names is ever fetched or opened, and no entity is expanded, so neither a
file that names a file of the server or an address (XXE) nor one that nests entities to grow
without end (the billion laughs) gets anywhere. The parser is told the same
(`disallow_entities`, `external_entities: :none`). A file that is not XML, whose root is not an
`<rss>` with a `<channel>`, or that breaks off is refused too; each is `{:error,
:unreadable_file}`, and a file that is not there `{:error, :no_file}`.

`php/1` reads the PHP-serialized values WordPress keeps in some meta, such as a file's sizes in
`_wp_attachment_metadata`, without running anything: arrays, strings, numbers, booleans and
`null`; an object, or anything else, reads as `nil`.

# `element`

```elixir
@type element() :: %{
  text: String.t(),
  attributes: %{optional(String.t()) =&gt; String.t()},
  children: %{optional(String.t()) =&gt; [element()]}
}
```

One element of the export: its text, its attributes and its children by name.

# `step`

```elixir
@type step(acc) :: {:cont, acc} | {:halt, acc}
```

What `fold/3`'s function answers for a child: `{:cont, acc}` to read on, `{:halt, acc}` to stop
reading the file there.

# `all`

```elixir
@spec all(element(), String.t()) :: [element()]
```

The element's children named `name`, in order.

# `attribute`

```elixir
@spec attribute(element(), String.t()) :: String.t() | nil
```

The element's attribute `name`, or `nil`.

# `fold`

```elixir
@spec fold(Path.t(), acc, (String.t(), element(), acc -&gt; step(acc))) ::
  {:ok, acc} | {:error, :no_file | :unreadable_file}
when acc: term()
```

Hands each child of the export's `<channel>` at `path` to `fun` with its qualified name, such as
`"item"` or `"wp:author"`, in the order of the file, from `acc`, and answers the last `acc`.

# `php`

```elixir
@spec php(String.t() | nil) :: term()
```

A PHP-serialized value as Elixir terms: an array whose keys are `0` to `n - 1` in order is a
list, any other a map with string keys. `nil` for an object, or for a value that is not one whole
serialized value.

# `text`

```elixir
@spec text(element()) :: String.t()
```

The element's own text, `""` for none.

# `text`

```elixir
@spec text(element(), String.t()) :: String.t() | nil
```

The text of the element's first child named `name`, or `nil` without one.

---

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