SweetXml Documentation

repository·master·Indexed 18 days ago

https://github.com/kbrw/sweet_xml

A thin Elixir wrapper around the Erlang :xmerl library that provides an idiomatic way to convert XML documents into Elixir data structures using XPath 1.0. It features the ~x sigil for defining paths with type modifiers, support for XML namespaces, and tools for mapping XML to nested maps and lists. For large files, it provides stream_tags/2 to process elements without loading the entire document into memory. It also includes security configurations for parsing untrusted XML by restricting DTD processing via the :dtd option.

Tokens
3.9K
Snippets
15
Records
17
Agent score
62%

What's inside SweetXml

  1. Use the ~x Sigil for XPath

    master

    The ~x sigil allows you to define XPath 1.0 paths. Warning: Because SweetXml uses xmerl internally, only XPath 1.0 paths are supported.

    If you do not import SweetXml, you must use the %SweetXpath{} struct instead of the sigil.

    # Using the sigil after importing
    import SweetXml
    ~x"//some/path"
    
    # Using the struct without importing
    %SweetXpath{path: '//some/path', is_value: false, is_list: false, cast_to: false}
  2. Map XML to Nested Structures

    master

    SweetXml allows you to transform XML into complex Elixir structures using xpath/2 or xmap/2. You can define nested maps and lists by passing additional arguments to xpath/2 or by nesting ~x expressions.

    # Mapping to a nested structure using xpath/2
    doc
    |> xpath(
      ~x"//li"l,
      name: [
        ~x"./name",
        first: ~x"./first/text()",
        last: ~x"./last/text()"
      ],
      age: ~x"./age/text()"i
    )
  3. Handle XML Namespaces

    master

    To support namespaces, you must parse the XML with the namespace_conformant: true option. You can then use add_namespace/2 to map preferred prefixes to the document's URIs, allowing you to query nodes in a prefix-independent way.

    import SweetXml
    
    # 1. Parse with namespace_conformant: true
    doc = parse(xml_str, namespace_conformant: true)
    
    # 2. Use add_namespace to define prefixes for your XPath
    result = doc
      |> xpath(~x"//ff:matchup/ff:name/text()"
               |> add_namespace("ff", "http://example.com/fantasy-league"))
  4. Install SweetXml

    master

    Add sweet_xml to your project's mix.exs dependencies.

    Note: SweetXml depends on :xmerl. On some Linux systems, you may need to manually install the erlang-xmerl package.

    def deps do
      [{:sweet_xml, "~> 0.7.5"}]
    end
  5. Securely parse untrusted XML documents

    master

    When processing XML from untrusted sources, you should separate the parsing step from the mapping step to prevent unintended behavior via options. It is highly recommended to disable DTD processing by passing dtd: :none to the parsing functions.

    For standard parsing and XPath extraction, use:

    doc |> SweetXml.parse(dtd: :none) |> SweetXml.xpath(spec, subspec)

    For streaming tags, use:

    enum |> SweetXml.stream_tags(tags, dtd: :none)
    # Recommended pattern for untrusted XML
    doc |> SweetXml.parse(dtd: :none) |> SweetXml.xpath(spec, subspec)
    
    # Recommended pattern for streaming untrusted XML
    enum |> SweetXml.stream_tags(tags, dtd: :none)
  6. Stream XML Tags

    master

    For large XML files, use stream_tags/2 to process specific elements one by one without loading the entire document into memory. This works with any Elixir stream, such as one created by File.stream!/1.

    Memory Management: When processing large documents, use the discard: [...] option in stream_tags/2 to prevent memory leaks by discarding processed tags.

    file_stream = File.stream!("large_file.xml")
    
    result = file_stream
    |> stream_tags([:li, :special_match_key], discard: [:li, :special_match_key])
    |> Stream.map(fn
        {_, doc} -> xpath(doc, ~x"./text()")
      end)
    |> Enum.to_list()
  7. Use the ~x sigil for XPath expressions

    master

    The ~x sigil is a convenient way to create %SweetXpath{} structs. It allows you to define XPath 1.0 paths with modifiers that control the return type and behavior. To use it, you should first import SweetXml.

    import SweetXml
    ~x"//some/path"l
  8. Configure DTD handling via the :dtd option

    master

    When parsing XML with SweetXml, you can control how Document Type Definitions (DTDs) and external entities are handled using the :dtd option. This is processed via SweetXml.Options.handle_dtd/2 internally.

    Supported values for the :dtd option:

    • :all: Allows all DTDs and entities (default behavior).
    • :none: Disallows external entities. If an external entity is encountered, it will trigger an error based on the provided exception_module.
    • :internal_only: Only allows internal entities. External entities will trigger an error.
    • [only: entity_name] or [only: [entity1, entity2, ...]]: A whitelist approach. Only the specified entities are allowed; all others will trigger an error.

    If you use :internal_only or a whitelist, you can specify which module to raise when a violation occurs. By default, this is RuntimeError or SweetXml.DTDError depending on the internal configuration.

  9. Secure XML parsing with DTD restrictions

    master

    When processing untrusted XML, it is highly recommended to restrict DTD (Document Type Definition) parsing to prevent attacks. Use the dtd option in parse/2.

    Options for :dtd:

    • :none: Prevents both internal and external entities. Recommended for untrusted XML.
    • :all: Allows all DTDs (default).
    • :internal_only: Blocks all attempts at external fetching.
    • [only: entities]: Only allows specific entities (where entities is an atom or a list of atoms).
    # Secure parsing pattern
    doc
    |> parse(dtd: :none)
    |> xmap(specs)
  10. Transform XPath Values with transform_by/2

    master

    You can apply Elixir functions to the results of an XPath query using transform_by/2. This is useful for data cleaning (e.g., capitalizing strings) or breaking complex parsing logic into reusable functions.

    # Using a built-in function
    doc |> xpath(
      ~x"//li"l,
      first: ~x"./first/text()"s |> transform_by(&String.capitalize/1)
    )
    
    # Using a custom function for complex nesting
    parse_name = fn xpath_node ->
      xpath_node |>
        xmap(
          first: ~x"./first/text()"s |> transform_by(&String.capitalize/1),
          last: ~x"./last/text()"s |> transform_by(&String.capitalize/1)
        )
    end
    
    doc |> xpath(
      ~x"//li"l,
      name: ~x"./name" |> transform_by(parse_name)
    )
  11. Reference: ~x Sigil Modifiers

    master

    Modifiers appended to the ~x sigil control the return type and casting of the XPath result.

    ~x"//some/path"          - Returns the value of the entity (xmlText, xmlAttribute, etc.)
    ~x"//some/path"e         - (e)ntity: Returns the :xmerl entity for further chaining
    ~x"//some/path"l         - (l)ist: Forces the result to be a list
    ~x"//some/path"k         - (k)eyword: Returns a Keyword instead of a Map
    ~x"//some/path"s         - (s)tring: Returns value as a string instead of a char list
    ~x"//some/path"S         - soft (S)tring: Returns string, or "" if incompatible
    ~x"//some/path"o         - (o)ptional: Returns nil if path does not exist
    ~x"//some/path"sl        - string list
    ~x"//some/path"i         - (i)nteger: Returns value as integer instead of a char list
    ~x"//some/path"I         - soft (I)nteger: Returns integer, or 0 if incompatible
    ~x"//some/path"f         - (f)loat: Returns value as float instead of a char list
    ~x"//some/path"F         - soft (F)loat: Returns float, or 0.0 if incompatible
    ~x"//some/path"il        - integer list
    ~x"//some/path"el        - A mix of the above modifiers
  12. Warning: SweetXml.Options is an internal API

    master

    The SweetXml.Options module is considered an internal API. It is used to manage the complex interaction between user-provided :rules (for XML scanning) and :dtd options.

    Use at your own risk. Changes to this module's behavior or function signatures are not guaranteed to be stable for end-users. Most users should interact with SweetXml through its high-level parsing functions rather than manually manipulating these options.