Giter VIP home page Giter VIP logo

xml_builder's Introduction

XML Builder

CI Module Version Hex Docs Total Download License Last Updated

Overview

An Elixir library for building XML. It is inspired by the late Jim Weirich's awesome builder library for Ruby.

Each XML node is structured as a tuple of name, attributes map, and content/list.

{name, attrs, content | list}

Installation

Add dependency to your project's mix.exs:

def deps do
  [{:xml_builder, "~> 2.1"}]
end

Examples

A simple element

Like <person id="12345">Josh</person>, would look like:

{:person, %{id: 12345}, "Josh"} |> XmlBuilder.generate

An element with child elements

Like <person id="12345"><first>Josh</first><last>Nussbaum</last></person>.

{:person, %{id: 12345}, [{:first, nil, "Josh"}, {:last, nil, "Nussbaum"}]} |> XmlBuilder.generate

Convenience Functions

For more readability, you can use XmlBuilder's methods instead of creating tuples manually.

XmlBuilder.document(:person, "Josh") |> XmlBuilder.generate

Outputs:

<?xml version="1.0" encoding="UTF-8" ?>
<person>Josh</person>

Building up an element

An element can be built using multiple calls to the element function.

import XmlBuilder

def person(id, first, last) do
  element(:person, %{id: id}, [
    element(:first, first),
    element(:last, last)
  ])
end

iex> [person(123, "Steve", "Jobs"),
      person(456, "Steve", "Wozniak")] |> generate

Outputs.

<person id="123">
  <first>Steve</first>
  <last>Jobs</last>
</person>
<person id="456">
  <first>Steve</first>
  <last>Wozniak</last>
</person>

Using keyed lists

The previous example can be simplified using a keyed list.

import XmlBuilder

def person(id, first, last) do
  element(:person, %{id: id}, first: first,
                              last: last)
end

iex> person(123, "Josh", "Nussbaum") |> generate(format: :none)
"<person id=\"123\"><first>Josh</first><last>Nussbaum</last></person>"

Namespaces

To use a namespace, add an xmlns attribute to the root element.

To use multiple schemas, specify a xmlns:nsName attribute for each schema and use a colon : in the element name, ie nsName:elementName.

import XmlBuilder

iex> generate({:example, [xmlns: "http://schemas.example.tld/1999"], "content"})
"<example xmlns=\"http://schemas.example.tld/1999\">content</example>"

iex> generate({:"nsName:elementName", ["xmlns:nsName": "http://schemas.example.tld/1999"], "content"})
"<nsName:elementName xmlns:nsName=\"http://schemas.example.tld/1999\">content</nsName:elementName>"

DOCTYPE declarations

A DOCTYPE can be declared by applying the doctype function at the first position of a list of elements in a document definition:

import XmlBuilder

document([
  doctype("html", public: ["-//W3C//DTD XHTML 1.0 Transitional//EN",
                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"]),
  element(:html, "Hello, world!")
]) |> generate

Outputs.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>Hello, world!</html>

Encoding

While the output is always UTF-8 and has to be converted in another place, you can override the encoding statement in the XML declaration with the encoding option.

import XmlBuilder

document(:oldschool)
|> generate(encoding: "ISO-8859-1")
|> :unicode.characters_to_binary(:unicode, :latin1)

Outputs.

<?xml version="1.0" encoding="ISO-8859-1"?>
<oldschool/>

Using iodata() directly

While by default, output from generate/2 is converted to binary(), you can use generate_iodata/2 to skip this conversion. This can be convenient if you're using IO.binwrite/2 on a :raw IO device, as these APIs can work with iodata() directly, leading to some performance gains.

In some scenarios, it may be beneficial to generate part of your XML upfront, for instance when generating a sitemap.xml, you may have shared fields for author. Instead of generating this each time, you could do the following:

import XmlBuilder

entries = [%{title: "Test", url: "https://example.org/"}]

# Generate static author data upfront
author = generate_iodata(element(:author, [
  element(:name, "John Doe"),
  element(:uri, "https://example.org/")
]))

file = File.open!("path/to/file", [:raw])

for entry <- entries do
  iodata =
    generate_iodata(element(:entry, [
      # Reuse the static pre-generated fields as-is
      {:iodata, author},

      # Dynamic elements are generated for each entry
      element(:title, entry.title),
      element(:link, entry.url)
    ]))

  IO.binwrite(file, iodata)
end

Escaping

XmlBuilder offers 3 distinct ways to control how content of tags is escaped and handled:

  • By default, any content is escaped, replacing reserved characters (& " ' < >) with their equivalent entity (&amp; etc.)
  • If content is wrapped in {:cdata, cdata}, the content in cdata is wrapped with <![CDATA[...]]>, and not escaped. You should make sure the content itself does not contain ]]>.
  • If content is wrapped in {:safe, data}, the content in data is not escaped, but will be stringified if not a bitstring. Use this option carefully. It may be useful when data is guaranteed to be safe (numeric data).
  • If content is wrapped in {:iodata, data}, either in the top level or within a list, the data is used as iodata(), and will not be escaped, indented or stringified. An example of this can be seen in the "Using iodata() directly" example above.

Standalone

Should you need standalone="yes" in the XML declaration, you can pass standalone: true as option to the generate/2 call.

import XmlBuilder

document(:outsider)
|> generate(standalone: true)

Outputs.

<?xml version="1.0" standalone="yes"?>
<outsider/>

If otherwise you need standalone ="no" in the XML declaration, you can pass standalone: false as an option to the generate / 2 call.

Outputs.

<?xml version="1.0" standalone="no"?>
<outsider/>

Formatting

To remove indentation, pass format: :none option to XmlBuilder.generate/2.

doc |> XmlBuilder.generate(format: :none)

The default is to formatting with indentation, which is equivalent to XmlBuilder.generate(doc, format: :indent).

License

This source code is licensed under the MIT License. Copyright (c) 2014-present, Joshua Nussbaum. All rights reserved.

xml_builder's People

Contributors

alappe avatar armatys avatar balexand avatar devstopfix avatar drozdoff avatar gatorjuice avatar jeroenvisser101 avatar jesteracer avatar johnhamelink avatar joshnuss avatar jstnjnsn avatar kianmeng avatar lucidstack avatar mainshayne233 avatar marcelotto avatar mmmries avatar plus17 avatar tomkonidas avatar wkirschbaum avatar zfletch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

xml_builder's Issues

Leverage IO lists to improve performance

Hello!

It seems that currently this lib works through binary concatenation. We should be able to improve encoding speed and reduce memory usage by using IO lists instead :)

I'd be great to see some benchmarks added and then then seeing how the IO data implementation compares.

Cheers,
Louis

`format: :none` is ignored when using XmlBuilder.doc

I am in the process of switching from pre-v1 to 2.0.0 and I'm struggling with the changes, especially around the different APIs of .doc and .generate. To get compact XML output I'm having to add empty maps on every element that has no attributes, and use .generate(format: :none), as if I use .doc at any time I get whitespace all over the XML document.

Is there a way I can avoid doing this? Thanks in advance!

Samples not working

Most of samples provided on readme not working

this is my module

defmodule Test do  
  def run do
    {:person, %{id: 12345}, "Josh"} |> XmlBuilder.generate
  end
end

After running Test.run this error comes:
** (FunctionClauseError) no function clause matching in XmlBuilder.generate/2
lib/xml_builder.ex:68: XmlBuilder.generate({:person, %{id: 12345}, "Josh"}, 0)

defmodule Test do
   def run do
     {:person, %{id: 12345}, [{:first, nil, "Josh"}, {:last, nil, "Nussbaum"}]} |> XmlBuilder.generate
   end 
end

When I run Test.run it is throwing error message:
screen: http://screencloud.net/v/bi2l
iex(1)> Test.run
** (FunctionClauseError) no function clause matching in XmlBuilder.generate/2
lib/xml_builder.ex:68: XmlBuilder.generate({:person, %{id: 12345}, [{:first, nil, "Josh"}, {:last, nil, "Nussbaum"}]}, 0)

I am running 0.0.7 version of xml_builder, also tried 0.0.6

This works

XmlBuilder.doc(:person, "Josh")

This sample not working:

defmodule Test do
  import XmlBuilder

  def run do
    [person(123, "Steve", "Jobs"),
       person(456, "Steve", "Wozniak")] |> generate
  end

  def person(id, first, last) do
     element(:person, %{id: id}, [
       element(:first, first),
       element(:last, last)
     ])
  end  
end

After running Test.run it is throwing this error
iex(1)> Test.run
** (FunctionClauseError) no function clause matching in XmlBuilder.element/1
lib/xml_builder.ex:35: XmlBuilder.element({:element, :first, nil, "Steve"})
(elixir) lib/enum.ex:1049: Enum.map/2
lib/xml_builder.ex:51: XmlBuilder.element/1
(ndc_ex_sdk) lib/test.ex:5: Test.run/0

screen: http://screencloud.net/v/y5Q0

This works fine

defmodule Test do
   import XmlBuilder

   def run do
      person(123, "Josh", "Nussbaum") |> generate
   end                                 

   def person(id, first, last) do      
     element(:person, %{id: id}, first: first, last: last)
   end 
end

Do I have any alternative to build xml with Elixir?

Add TravisCI integration.

Would you mind to add a TravisCI integration?

It is as easy as follow the link, register there with your GH OAuth, and then put to the project root the file named .travis.yml having the following content:

language: elixir
elixir:
  - 1.3.4
after_script:
  - MIX_ENV=docs mix deps.get
  - MIX_ENV=docs mix inch.report

That way you’ll get all tests running for free on each subsequent pull request etc.

Unfortunately, I cannot do that for you since it establishes a link to the repo and since you are the maintainer...

How to deal with namespaces?

It is unclear to me how to create Xml with namespaces.
For example:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <IndienenBerichtResponse xmlns="http://schemas.vecozo.nl/berichtuitwisseling/v3"> <IndienenBerichtResult> <ConversatieId xmlns="http://schemas.vecozo.nl/berichtuitwisseling/messages/v3">fc6d5c90-5630-4270-aba2-bc1be7936502</ConversatieId> <TraceerId xmlns="http://schemas.vecozo.nl/berichtuitwisseling/messages/v3">fc6d5c90-5630-4270-aba2-bc1be7936502</TraceerId> </IndienenBerichtResult> </IndienenBerichtResponse> </s:Body> </s:Envelope>

How to add s as a namespace using generate?

Generating XML values for ASCII characters like newline or cr

Amazon s3 key names with ASCII chars like \n or \r are expected to be mapped in XML data to like "& # 13; " or "& # 10 ;" (added spaces to ensure all would be seen here) or the like. Can I motivate XmlBuilder to generate that? When I use XmlBuilder.generate with character data with those special characters in it, it is not happening like this:

iex(17)> {:person, %{id: 12345}, "Josh\n"} |> XmlBuilder.generate
"<person id=\"12345\">Josh\n</person>"

...where I would like it to generate:

"<person id=\"12345\">Josh&#10;</person>"

I looked in the tests to see if there are any examples of this and I did not see any...

Thanks in advance for any help on this.

Backslash character remove from xml issue

How to remove the backslash character from xml in elixir:-
String.replace(""", """) is not working for my case.

I need String.replace("", ""). Please suggest how to achieve it.

Not able to use XML Entities

I'm trying to add a linefeed in my XML but it's translating & like such:

iex(11)> XmlBuilder.generate({:thing, %{}, "hello &#xA; world"})
"<thing>hello &amp;#xA; world</thing>"

Is there a way to escape characters or to straight up use xml entities?

Minified Output as Option

It is useful in some circumstances to generate XML without indentation or new lines, as such, I propose generate and related methods take an optional parameter that specifies if indentation and new lines should be included in output.

Currently, the only work around is to write a complex regex that removes whitespace after xml_builder is called, that strips the whitespace generated by xml_builder, but leaves whitespace where it occurs inside of data passed into xml_builder.

Support Elixir 0.15.0

Is there any plan to release an update that support elixir 0.15.X? If I pull down the project and try to get all the tests passing on 0.15.0 can I just send a Pull Request?

XmlBuilder does not generate second ? in doc type

iex(2)> {:a} |> XmlBuilder.doc
"<?xml version=\"1.0\">\n<a/>"
iex(3)> XmlBuilder.generate(:_doc_type, 0)
"<?xml version=\"1.0\">"

Second ? is in the first line is missing.

Elixir 1.0.5, xml_builder 0.0.6

standalone false option

Hi, thanks for your work with XML builder :)

Currently, we have an issue with the standalone option. We are consuming a service that, apparently breaks if we don't send the standalone with no value:

<?xml version="1.0" standalone="no"?>

Do you consider implementing this behavior when explicitly pass a false value? Something like:

import XmlBuilder

document(:outsider)
|> generate(standalone: false)

Output:

<?xml version="1.0" standalone="no"?>
<outsider/>

to_string fails for some reason

I've this xml structure

{"SOAP-ENV:Envelope", %{"xlmns:SOAP-ENV" => "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:ns1" => "url"},
  [
    {"SOAP-ENV:Header", nil,
      {"ns1:UserCredentials", nil,"test"}
    }
  ]
}
|> XmlBuilder.generate

this gives

     ** (Protocol.UndefinedError) protocol String.Chars not implemented for {"ns1:UserCredentials", nil, "test"}
     stacktrace:
       (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
       (elixir) lib/string/chars.ex:17: String.Chars.to_string/1
       lib/xml_builder.ex:122: XmlBuilder.escape/1
       lib/xml_builder.ex:75: XmlBuilder.generate/2
       (elixir) lib/enum.ex:1215: anonymous fn/4 in Enum.map_join/3
       (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
       (elixir) lib/enum.ex:1214: Enum.map_join/3
       lib/xml_builder.ex:90: XmlBuilder.generate_content/2
       lib/xml_builder.ex:84: XmlBuilder.generate/2
       (project_connector) lib/project/client.ex:20: Project.Client.call/2
       (project_connector) lib/project/client.ex:5: Project.Client.get_teams/0
       test/project/client_test.exs:10: (test)

if I replace the userCredentials instead it works fine, why?

{"SOAP-ENV:Envelope", %{"xlmns:SOAP-ENV" => "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:ns1" => "test"},
  [
    {"SOAP-ENV:Header", nil, "test"
    }
  ]
}
|> XmlBuilder.generate

is there a nesting limit?

Issue creating an xml doc with the following specification

Is it possible to create an XML doc with content like this?

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="com.ao.inoka">
	<soap:Header/>
	<soap:Body>
         <shoppingCart>
            <items>
               <amountPerItem>1.00</amountPerItem>
               <count>1</count>
               <description>Bilhete</description>
               <discount>0</discount>
               <totalAmount>1.00</totalAmount>
            </items> 
         </shoppingCart>
         <externalReference>18/21019</externalReference>
	 </com:initiateMobilePaymentViaOtpRequestFromMerchant>
	</soap:Body>
</soap:Envelope>

Using if condition to make tags

This is more of a question than an issue. Is there a way to use if condition inside element tags?
e.g.
element(:tags,
if (x == true)
[element(:true)]
else
[element(:false)])

Attribute encoding, alters url, no way of sending {:safe, url} on attribute

Hiya,

When creating an atom link like so:

 XmlBuilder.element(
      :"atom:link",
      %{
        rel: :next,
        href: "http://dfp.localhost:4001/productions?brand=24Kitchen&offset=50"
      }
    )

we get an link like so

<atom:link href="http://dfp.localhost:4001/productions?brand=24Kitchen&amp;offset=50" rel="next"/>

Only now the & is encoded to &amp; because the attributes get encoded. Only for an Url with query parameters this is not the desired outcome.

Ive tried something like this:

    XmlBuilder.element(
      :"atom:link",
      %{
        rel: :next,
        href: {:safe, "http://dfp.localhost:4001/productions?brand=24Kitchen&offset=50"}
      }
    )

Only this results In the following error:

protocol String.Chars not implemented for {:safe, "http://dfp.localhost:4001/productions?brand=24Kitchen&offset=50"} of type Tuple

Thanks in advance!

The Repo on Hex isn't updated

When getting xml_builder from hex {:xml_builder, "~> 0.1.1"} the function doctype is missing!

When getting xml_builder directly from Github {:xml_builder, git: "https://github.com/joshnuss/xml_builder"} the function doctype is avalible

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.