czrpb

czrpb

Modifying an XML doc - parsing and writing back out to file

I would like to modify an XML file; meaning parsing and writing back out to file.

Lets make it simple: i want to uppercase all b elements with a name attribute.

ORIGINAL

<a>
   <b name="sam">text</b>
   <c name="sal">text</b>
   <b title="bob">text</b>
</a>

UPDATED

<a>
   <b name="SAM">text</b>
   <c name="sal">text</b>
   <b title="bob">text</b>
</a>

ive spent a reasonable amount of time searching for examples of reading and writing, thus updating an XML string/file but just can not seem to find anything.

sweet_xml has been great, but i cant see how to use it to modify and write an xml string (file).

What am I missing in the elixir ecosystem to read/transform/write XML?

thx!! << q

Marked As Solved

zachallaun

zachallaun

Awesome! Glad you got it figured out.

A minor suggestion: use IO data instead of concatenating strings directly. IO data is a sort of composite data type meant for this exact use-case, where the result is modeled as an arbitrarily nested list of strings/characters/etc. In the example below, I’m using IO.chardata_to_string/1, but if you’re just writing it back out to a file and have no use for further string processing, you can actually pass the chardata directly to most (all?) IO functions!

For a super small example the runtime will be essentially the same, but using IO data will definitely be faster for large files (and I think the resulting code is a bit cleaner).

Mix.install([
  {:saxy, "~> 1.4.0"}
])

defmodule ExampleHandler do
  @behaviour Saxy.Handler

  def parse_stream!(xml_stream) do
    {:ok, rev_chardata} = Saxy.parse_stream(xml_stream, __MODULE__, [])

    rev_chardata
    |> Enum.reverse()
    |> IO.chardata_to_string()
  end

  def build(:open, tag, attrs) do
    encoded_attrs = Enum.map(attrs, fn {name, val} -> [" ", name, "=\"", val, "\""] end)
    ["<", tag, encoded_attrs, ">"]
  end

  def build(:close, tag) do
    ["</", tag, ">"]
  end

  def handle_event(:start_element, {"b", [{"name", name}]}, state) do
    {:ok, [build(:open, "b", [{"name", String.upcase(name)}]) | state]}
  end

  def handle_event(:start_element, {tag, attrs}, state) do
    {:ok, [build(:open, tag, attrs) | state]}
  end

  def handle_event(:end_element, tag, state) do
    {:ok, [build(:close, tag) | state]}
  end

  def handle_event(:characters, cdata, state) do
    {:ok, [cdata | state]}
  end

  def handle_event(_, _, state), do: {:ok, state}
end

[xmlfile | _] = System.argv()

IO.puts("Processing #{xmlfile}")

ExampleHandler.parse_stream!(File.stream!(xmlfile))
|> IO.puts()

Works fine with unicode too =)

> elixir saxy_example.exs example.xml
<a>
   <b name="SAM">π</b>
   <c name="sal">text</c>
   <b title="bob">text</b>
</a>

Also Liked

zachallaun

zachallaun

I’d recommend looking at saxy, which is both a parser and encoder that should enable what you’re looking for.

al2o3cr

al2o3cr

Don’t make XML by gluing together strings - it’s too easy to create bugs.

For instance, ExampleHandler will produce invalid XML when given this document:

<something>
  <b name="foo">blargh</b>
  <b name="foo&quot;bar">baz</b>
  <z nothing="nope" />
</something>

the output fails to re-escape the double-quote character in the second element:

# result from ExampleHandler
<something >
  <b name="FOO">blargh</b>
  <b name="FOO"BAR">baz</b>
  <z nothing="nope"></z>
</something>

(it also adds stray blanks to the end of tags like something and swaps z to the other format, but IIRC both of those aren’t semantic changes)

zachallaun

zachallaun

You can use something like XmlBuilder as well. Here’s how it handles escaping.

Edit: also relevant StackOverflow about XML escaping requirements.

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New

We're in Beta

About us Mission Statement