defmodule Bio.XML do # TODO: should I expose this? it's mostly test helpers @moduledoc false @doc """ Read an XML file and return the document without the rest This function makes the assumption that you are parsing out a rooted document and that you don't care about anything that might be in rest. If you don't know, `:xmerl_scan.file/1` will return a tuple where the first element is the first root document and the second is a charlist of the next sibling. By way of example, the following: ``` ``` Would return `{doc, ''}`. This function would ignore that. If you're working with XML that you might want to deal with this case, this is not the function for you! """ def read(filename) do {doc, _rest} = :xmerl_scan.file(filename) doc end @doc """ Get an element from an XML document Simple proxy call to `:xmerl_xpath.string/2`, but it switches the position of the arguments to make it easier to pipe a doc through. This plays nicely with the `Bio.XML.read/1` function, since you can now do things like: iex>Bio.XML.read("some.xml") |> Bio.XML.get('/*/Some/Thing') """ def get(doc, path) do :xmerl_xpath.string(path, doc) end @doc """ Retrieve [path] with `string` called on it from [doc] This is a proxy call to `:xmerl_xpath.string/2` where the arguments are reversed, and the path is interpolated into the XML `string` function. It also unpacks the tuple, returning only the value as a binary instead of a charlist. """ def str(doc, path) do {_, _, value} = :xmerl_xpath.string('string(#{path})', doc) List.to_string(value) end @doc """ Call `Bio.XML.str` on the root node of the document This is a pure convenience for mapping over collections of selected nodes where you want to get the string value. For example, if you have: ``` ``` Then you can easily get the names with: iex> Bio.XML.read('that_file.xml') |> Bio.XML.get('/*/Thing/@name') |> Enum.map(&Bio.XML.root_str/1) """ def root_str(doc) do str(doc, '/*') end @doc """ Collect elements of the path using `Bio.XML.root_str/1` This abstracts the pattern of making a selection and mapping over it to produce a list of strings. """ def collect(doc, path) do doc |> get(path) |> Stream.map(&root_str/1) |> Enum.into([]) end @doc """ Collect elements of the path using a given transformation Generalizes `Bio.XML.collect/2` by allowing you to define the transform on each element. """ def collect(doc, path, transform) do doc |> get(path) |> Stream.map(fn el -> transform.(el) end) |> Enum.into([]) end end