First post: Noob question. Writing to a file can't be this difficult, right?

Hey Elixir Forum. This is my first time using Elixir for actual professional use, and not just messing around with it in my own free time.

So, basically I’m recursively going through an entire directory of sub-directories. I’m saving all of the file paths in those directories as a list. I take that list of files and parse the xml value I want out of them. I store this as a map of :filename => [xml-values]. It’s important I have the filename associated with the value. From here I want to write my map that I created to a file. I don’t really have a preference at this point because I’ve gotten very frustrated. I just need it human readable. JSON, CSV, text . . . doesn’t matter. How in the world can I the entire directory of xml values to a file while avoiding overwriting the file & while avoiding storing the XML values as binary? I’ve been trying IO.write & File.write in many many different variations.

Here is a bit of the code:

 def read do
    files = list_all("/my/files/path/right/here/right/now")

    for file <- files do
      doc = File.read!(file)

      xml = doc |> xpath(~x"//MeshGeometry3D/@x:Key"l)

      list_prefix = %{file => xml}
end

Thanks for any replies. This morning I felt confident I had this problem in the bag. Now I’m exhausted just looking at it.

(Lots of edits for clarification)

1 Like

You aren’t actually trying to write something. I’d suggest showing us some code where you try to write a file. Also I think it might be useful if you tell us which library you use for xml parsing.

1 Like

Ah, my mistake. I’m using SweetXML to parse the xml.

  def write do
    myfile = read()
    File.write("/my/file/path/here/access_components", myfile)
  end

I had a different solution where I was closer, but I suspect the map I created keeps overwriting itself with new values. I get confused because if I IO.inspect inside that loop it looks like it is doing what I want in that it writes thousands of lines with %{filename => [list_values]}.

That’s because you are appending line to file and not creating big map and save it once. Also are you saving it using inspect(map)?

1 Like

No I’m not saving it with inspect(map). Yeah, I need to create a big map and save it at once. However, when I try something like that such as

 list_prefix = %{file => doc}

 list_prefix = Map.put(list_prefix, file => doc)

This doesn’t work so well because it stops using the filename as the key and just puts “file” as the key. Or it will error out saying “file” must be an atom.

Can you give me part of code to fetch xml values and example file (not with real data) so I will give you full example script? I did not worked yet with xml files. :slight_smile:

1 Like

It is difficult to share the data because it is structured like:

root_directory
child_directory -> has_xml_files
child_directory -> has_xml_files
child_directory -> has_xml_files
child_directory -> has_xml_files

My very first post has the code I use to parse the xml. Just import SweetXML. I’ll see what I can do when I get home.

No, I just need one example (but valid) .xml file + code to fetch xml values from it to make sure it works correctly.

1 Like

@Johnathonjelly: ok, I got example xml and way to fetch values from sweet_xml GitHub page:

<?xml version="1.05" encoding="UTF-8"?>
<game>
  <matchups>
    <matchup winner-id="1">
      <name>Match One</name>
      <teams>
        <team>
          <id>1</id>
          <name>Team One</name>
        </team>
        <team>
          <id>2</id>
          <name>Team Two</name>
        </team>
      </teams>
    </matchup>
    <matchup winner-id="2">
      <name>Match Two</name>
      <teams>
        <team>
          <id>2</id>
          <name>Team Two</name>
        </team>
        <team>
          <id>3</id>
          <name>Team Three</name>
        </team>
      </teams>
    </matchup>
    <matchup winner-id="1">
      <name>Match Three</name>
      <teams>
        <team>
          <id>1</id>
          <name>Team One</name>
        </team>
        <team>
          <id>3</id>
          <name>Team Three</name>
        </team>
      </teams>
    </matchup>
  </matchups>
</game>

Here is example script which uses it:

defmodule Example do
  import SweetXml

  def sample(path \\ ".") do
    content =
      path
      |> list_xml_files_recursively()
      |> Enum.map(&get_xml_values/1)
      |> Enum.into(Map.new())
      |> :erlang.term_to_binary()

    File.write!("results.bert", content)
  end

  defp list_xml_files_recursively(path) do
    cond do
      File.regular?(path) ->
        [path]

      File.dir?(path) ->
        path
        |> File.ls!()
        |> Enum.map(&Path.join(path, &1))
        |> Enum.filter(&file_or_xml/1)
        |> Enum.flat_map(&list_xml_files_recursively/1)

      true ->
        []
    end
  end

  defp file_or_xml(path), do: File.dir?(path) or Path.extname(path) == ".xml"

  defp get_xml_values(path) do
    values = path |> File.read!() |> xpath(~x"//matchup/name/text()"l)
    {path, values}
  end
end

After running Example.sample("your/optional/path/goes/here") you need to read results.bert file simply like:

"results.bert" |> File.read!() |> :erlang.binary_to_term()

In my case I have put example xml to 2 files: example.xml and sample.xml. Here is example response after reading results.bert file:

%{
  "./example.xml" => ['Match One', 'Match Two', 'Match Three'],
  "./sample.xml" => ['Match One', 'Match Two', 'Match Three']
}
6 Likes

Ohhhh, very nice! You have been so patient and nice with me! I remember when I very very first started to learn programming a few years ago everyone was difficult (stackoverflow). Thank you so much. This is my first month of functional programming so I know my code might have been difficult to look at. I think your solution will really help me out!

5 Likes