defmodule Soap.Wsdl do @moduledoc """ Provides functions for parsing wsdl file """ @soap_version_namespaces %{ "1.1" => :"http://schemas.xmlsoap.org/wsdl/soap/", "1.2" => :"http://schemas.xmlsoap.org/wsdl/soap12/" } import SwXml, except: [parse: 1, parse: 2] alias Soap.{Type, Xsd} def imports do {_,wsdl} = :file.read_file('priv/sevovv/tempuri.org.wsdl') Soap.Wsdl.get_full_paths(wsdl, [], 'wsdl', 'xsd') |> Soap.Wsdl.get_imported_types end @spec parse_from_file(String.t()) :: {:ok, map()} def parse_from_file(path, opts \\ []) do {:ok, wsdl} = File.read(path) parse(wsdl, path, opts) end @spec parse_from_url(String.t()) :: {:ok, map()} def parse_from_url(path, opts \\ []) do {:ok,{{_,200,_},_,wsdl}} = :httpc.request(:get, {(path |> :erlang.binary_to_list),[]},[{:relaxed,true},{:timeout,5000}], []) parse(wsdl |> :erlang.list_to_binary, path, opts) end @spec parse(String.t(), String.t()) :: {:ok, map()} def parse(wsdl, file_path, opts \\ []) do protocol_namespace = get_protocol_namespace(wsdl) soap_namespace = get_soap_namespace(wsdl, opts) schema_namespace = get_schema_namespace(wsdl) parsed_response = %{ namespaces: get_namespaces(wsdl, schema_namespace, protocol_namespace), endpoint: get_endpoint(wsdl, protocol_namespace, soap_namespace), complex_types: get_complex_types(wsdl, schema_namespace, protocol_namespace), operations: get_operations(wsdl, protocol_namespace, soap_namespace), schema_attributes: get_schema_attributes(wsdl), validation_types: get_validation_types(wsdl, file_path, protocol_namespace, schema_namespace), soap_version: soap_version(opts), messages: get_messages(wsdl, protocol_namespace) } {:ok, parsed_response} end @spec get_schema_namespace(String.t()) :: String.t() def get_schema_namespace(wsdl) do {_, _, _, schema_namespace, _} = wsdl |> xpath(~x"//namespace::*"l) |> Enum.find(fn {_, _, _, _, x} -> x == :"http://www.w3.org/2001/XMLSchema" end) schema_namespace end @spec get_namespaces(String.t(), String.t(), String.t()) :: map() def get_namespaces(wsdl, schema_namespace, protocol_ns) do wsdl |> xpath(~x"//#{ns("definitions", protocol_ns)}/namespace::*"l) |> Enum.into(%{}, &get_namespace(&1, wsdl, schema_namespace, protocol_ns)) end @spec get_namespace(map(), String.t(), String.t(), String.t()) :: tuple() def get_namespace(namespaces_node, wsdl, schema_namespace, protocol_ns) do {_, _, _, key, value} = namespaces_node string_key = key |> to_string value = Atom.to_string(value) cond do xpath(wsdl, ~x"//#{ns("definitions", protocol_ns)}[@targetNamespace='#{value}']") -> {string_key, %{value: value, type: :wsdl}} xpath( wsdl, ~x"//#{ns("types", protocol_ns)}/#{ns("schema", schema_namespace)}/#{ns("import", schema_namespace)}[@namespace='#{ value }']" ) -> {string_key, %{value: value, type: :xsd}} true -> {string_key, %{value: value, type: :soa}} end end @spec get_endpoint(String.t(), String.t(), String.t()) :: String.t() def get_endpoint(wsdl, protocol_ns, soap_ns) do wsdl |> xpath( ~x"//#{ns("definitions", protocol_ns)}/#{ns("service", protocol_ns)}/#{ns("port", protocol_ns)}/#{ ns("address", soap_ns) }/@location"s ) end @spec get_complex_types(String.t(), String.t(), String.t()) :: list() def get_complex_types(wsdl, namespace, protocol_ns) do xpath( wsdl, ~x"//#{ns("types", protocol_ns)}/#{ns("schema", namespace)}/#{ns("element", namespace)}"l, name: ~x"./@name"s, type: ~x"./@type"s ) end @spec get_validation_types(String.t(), String.t(), String.t(), String.t()) :: map() def get_validation_types(wsdl, file_path, protocol_ns, schema_ns) do Map.merge( Type.get_complex_types( wsdl, "//#{ns("types", protocol_ns)}/#{ns("schema", schema_ns)}/#{ns("complexType", schema_ns)}" ), wsdl |> get_full_paths(file_path, protocol_ns, schema_ns) |> get_imported_types |> Enum.reduce(%{}, &Map.merge(&2, &1)) ) end @spec get_schema_imports(String.t(), String.t(), String.t()) :: list() def get_schema_imports(wsdl, protocol_ns, schema_ns) do xpath( wsdl, ~x"//#{ns("types", protocol_ns)}/#{ns("schema", schema_ns)}/#{ns("import", schema_ns)}"l, schema_location: ~x"./@schemaLocation"s ) end @spec get_full_paths(String.t(), String.t(), String.t(), String.t()) :: list(String.t()) def get_full_paths(wsdl, path, protocol_ns, schema_namespace) do wsdl |> get_schema_imports(protocol_ns, schema_namespace) |> Enum.map(&(path |> Path.dirname() |> Path.join(&1.schema_location))) end @spec get_imported_types(list()) :: list(map()) def get_imported_types(xsd_paths) do xsd_paths |> Enum.map(fn xsd_path -> case Xsd.parse(xsd_path) do {:ok, xsd} -> xsd.complex_types _ -> %{} end end) end def get_operations(wsdl, protocol_ns, soap_ns) do wsdl |> xpath(~x"//#{ns("definitions", protocol_ns)}/#{ns("binding", protocol_ns)}/#{ns("operation", protocol_ns)}"l) |> Enum.map(fn node -> node |> xpath(~x".", name: ~x"./@name"s, soap_action: ~x"./#{ns("operation", soap_ns)}/@soapAction"s) |> Map.put(:input, get_operation_input(node, protocol_ns, soap_ns)) end) |> Enum.reject(fn x -> x[:soap_action] == "" end) end def get_operation_input(element, protocol_ns, soap_ns) do case xpath(element, ~x"./#{ns("input", protocol_ns)}/#{ns("header", soap_ns)}") do nil -> %{ body: nil, header: nil } header_node -> %{ body: nil, header: xpath(header_node, ~x".", message: ~x"./@message"s, part: ~x"./@part"s) } end end def get_messages(wsdl, protocol_ns) do wsdl |> xpath(~x"//#{ns("definitions", protocol_ns)}/#{ns("message", protocol_ns)}"l) |> Enum.map(fn node -> node |> xpath(~x".", name: ~x"./@name"s) |> Map.put(:parts, get_message_parts(node, protocol_ns)) end) end def get_message_parts(element, protocol_ns) do xpath(element, ~x"./#{ns("part", protocol_ns)}"l, name: ~x"./@name"s, element: ~x"./@element"s) end @spec get_protocol_namespace(String.t()) :: String.t() def get_protocol_namespace(wsdl) do wsdl |> xpath(~x"//namespace::*"l) |> Enum.find(fn {_, _, _, _, url} -> url == :"http://schemas.xmlsoap.org/wsdl/" end) |> elem(3) end @spec get_soap_namespace(String.t(), list()) :: String.t() def get_soap_namespace(wsdl, opts) when is_list(opts) do version = soap_version(opts) namespace = @soap_version_namespaces[version] wsdl |> xpath(~x"//namespace::*"l) |> Enum.find(fn {_, _, _, _, url} -> url == namespace end) |> elem(3) end @spec get_schema_attributes(String.t()) :: map() def get_schema_attributes(wsdl) do wsdl |> xpath( ~x"//*[local-name() = 'schema']", target_namespace: ~x"./@targetNamespace"s, element_form_default: ~x"./@elementFormDefault"s ) end def soap_version, do: Application.fetch_env!(:sms_trembita, :globals)[:version] def soap_version(opts) when is_list(opts), do: Keyword.get(opts, :soap_version, soap_version()) def ns(name, []), do: "#{name}" def ns(name, namespace), do: "#{namespace}:#{name}" end