# `Dmn.XML`
[🔗](https://github.com/FeeJai/BDM/blob/dmn-v0.1.0/apps/dmn/lib/dmn/xml.ex#L1)

Reads an XML document that nobody vouches for.

A DMN model is a file from outside, and it names other files to import. `boxic` reads them with `:xmerl_scan`, which makes an
atom for every name of an element and an attribute (6,000 names make 6,000 atoms, which is measured), so a document with
enough different names ends the VM: an atom is never freed, and the table is full at about a million. Whether it also expands
entities (a few lines of `<!ENTITY>` make gigabytes, and an external entity reads a file or a URL) depends on the version of
OTP and on the options it is called with, and a reader of untrusted documents should depend on neither. This reader is for
the opposite case.

  * **`:xmerl_sax_parser`, never `:xmerl_scan`.** The SAX parser makes no atoms from the document (the names are lists of
    characters, which this module makes into strings), and builds no tree of its own.
  * **No `DOCTYPE` that declares or refers to anything.** A document type declaration is where entities are declared (and where
    an external DTD is named, and attributes get defaults), so one that has an internal subset or an external identifier is an
    error (`:doctype_not_allowed`) the moment the parser reports it, before it reads what is inside. A bare `<!DOCTYPE name>`
    declares nothing, and the parser does not report it. The five predefined entities and character references are read as
    XML says. No external entity or external DTD is ever read.
  * **Limits** (`Dmn.Limits`): the size of the document before anything is read, then the depth, the number of elements, the
    attributes of an element and the length of a name or of a namespace URI as the events come. A document that passes one is an error of the
    kind `:limit`.
  * **A process that can be killed** (`Feel.Worker`). The SAX parser takes time quadratic in the attributes of one element
    and cannot be stopped, so the document is read in a process with a deadline and a cap on its memory.
  * **A limit on numeric character references.** A character is written with up to 7 decimal digits or 6 hex digits (U+10FFFF is
    `&#x10FFFF;`), and a reference of more than 10 decimal or 8 hex digits is an error (`:character_reference_too_large`),
    found in the bytes before the parser is called. That look is at bytes that are ASCII, so a document that has a NUL byte,
    which is how UTF-16 and UTF-32 write ASCII, is an error too (`:unsupported_encoding`).
  * **UTF-8**, with or without a byte order mark, or an encoding that has ASCII for its bytes and that the XML declaration names
    (`ISO-8859-1`). Not UTF-16 or UTF-32. A name with a prefix that is not declared is an error.

The result is a tree of `Dmn.XML.Element`: names are strings, the namespaces are resolved, and the text and the attributes
are strings. Comments and processing instructions are dropped, a CDATA section is text. An element keeps the prefixes that it
declares itself and not those in scope at it: the tree is sent from the process that reads it to the caller, a message does not
keep sharing, and a map of every prefix in scope on each of 300 leaves of a deep document is 367 MB for a document of 168 kB.
A reader that needs a prefix (`Dmn.Reader`, for a `typeRef`) walks down with the scope (`Dmn.XML.Element.enter/2`).

This reads XML and not DMN. It does not know the schema; `Dmn.Reader` does.

    iex> {:ok, root} = Dmn.XML.parse(~s(<a xmlns="urn:x"><b id="1">text</b></a>))
    iex> {root.namespace, root.name, hd(root.children).attributes, hd(root.children).text}
    {"urn:x", "a", %{"id" => "1"}, "text"}
    iex> {:error, error} = Dmn.XML.parse(~s(<!DOCTYPE a [<!ENTITY x "y">]><a>&x;</a>))
    iex> error.code
    :doctype_not_allowed

# `parse`

```elixir
@spec parse(binary(), keyword()) ::
  {:ok, Dmn.XML.Element.t()} | {:error, Dmn.Error.t()}
```

Reads a document.

Options: `:limits`, a `t:Dmn.Limits.t/0` or the fields of one to replace; limits that are neither, or have a value that is not a positive
integer, are `{:error, %Dmn.Error{kind: :model, code: :invalid_options}}` (DD-39), as for `Dmn.load/2`.

Returns the root element, or an error of the kind `:xml` (the document is not well formed, or it has a `DOCTYPE`, or a name
has a prefix that is not declared, or it is in UTF-16 or UTF-32, or has a numeric character reference of more digits than a
character has) or `:limit`. Nothing raises for the document.

---

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