Skip to content

Potentially wrong output behavior when an attribute references the default namespace  #28

Description

@mben-romdhane

According to the XML namespace specifications, the default namespace does not apply directly to the attributes, it applies to unprefixed elements, and elements determine the behavior of unprefixed attributes. So, I would expect that output_tree/output_doc_tree would raise if we try to print an attribute whose namespace is:

  1. the default namespace.
  2. is not bound to any prefix.

However, output_tree/output_doc_tree happily chop away the namespace if it's equal to the default namespace. Additionally, they generate the same output for the attributes whether the attribute is the default namespace or whether the attribute is unprefixed, which is also incorrect (I think) because they have different meanings.

Here's a test that reproduces the issue and demonstrates the various cases:

module Simple = struct
  open! Xmlm

  type attribute =
    | Default_namespace of string
    | Namespace_prefix of
        { prefix : string
        ; namespace : string
        }
    | Generic of { namespace : string }

  type t =
    { namespace : string
    ; attributes : attribute list
    ; children : t list
    }

  let to_string t =
    let buffer = Buffer.create 16 in
    let output = make_output ~indent:(Some 2) (`Buffer buffer) in
    output_doc_tree
      (fun { namespace; attributes; children } ->
         let attributes =
           List.map
             (function
               | Default_namespace namespace -> (ns_xmlns, "xmlns"), namespace
               | Namespace_prefix { prefix; namespace } -> (ns_xmlns, prefix), namespace
               | Generic { namespace } -> (namespace, "generic"), namespace)
             attributes
         in
         `El (((namespace, "element"), attributes), children))
      output
      (None, t);
    Buffer.contents buffer
  ;;
end

let%expect_test "Xmlm" =
  let open! Xmlm in
  let default_ns = "default-ns" in
  let element_ns = "element-ns" in
  let print ~attribute_namespace =
    Simple.to_string
      { namespace = ""
      ; attributes =
          [ Default_namespace default_ns
          ; Namespace_prefix { prefix = "element"; namespace = element_ns }
          ]
      ; children =
          [ { namespace = element_ns
            ; attributes = [ Generic { namespace = attribute_namespace } ]
            ; children = []
            }
          ]
      }
    |> print_endline
  in
  (* When the attribute's namespace is the default namespace. *)
  print ~attribute_namespace:default_ns;
  [%expect
    {|
    <?xml version="1.0" encoding="UTF-8"?>
    <element xmlns="default-ns" xmlns:element="element-ns">
      <element:element generic="default-ns"/>
    </element> |}];
  (* When the attribute's namespace is not specified. *)
  print ~attribute_namespace:"";
  [%expect
    {|
    <?xml version="1.0" encoding="UTF-8"?>
    <element xmlns="default-ns" xmlns:element="element-ns">
      <element:element generic=""/>
    </element> |}];
  (* When the attribute's namespace is a bound namespace. *)
  print ~attribute_namespace:element_ns;
  [%expect
    {|
    <?xml version="1.0" encoding="UTF-8"?>
    <element xmlns="default-ns" xmlns:element="element-ns">
      <element:element element:generic="element-ns"/>
    </element> |}]
;;

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions