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 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:comments.
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.
Summary
Types
One element of the export: its text, its attributes and its children by name.
Functions
The element's children named name, in order.
The element's attribute name, or nil.
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.
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.
The element's own text, "" for none.
The text of the element's first child named name, or nil without one.
Types
@type element() :: %{ text: String.t(), attributes: %{optional(String.t()) => String.t()}, children: %{optional(String.t()) => [element()]} }
One element of the export: its text, its attributes and its children by name.
@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.
Functions
The element's children named name, in order.
The element's attribute name, or nil.
@spec fold(Path.t(), acc, (String.t(), element(), acc -> 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.
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.
The element's own text, "" for none.
The text of the element's first child named name, or nil without one.