Can't use (Erlang) Records that start with an uppercased letter

So I am using Erlsom to parse a XML file that also has a matching XSD file. Erlsome includes an option to generate records (.erl file) based on the model of the XSD (erlsom:write_xsd_hrl_file).

This auto-generated .erl file includes records that start with an uppercased letter. For example:

-record('Pip3A4PurchaseOrderRequestType', {anyAttribs :: anyAttribs(),
	fromRole :: roleType(),
	'Authentication' :: 'AuthenticationType'(),
	'GlobalDocumentFunctionCode' :: string(),
	'PurchaseOrder' :: 'PurchaseOrderType'(),
	thisDocumentGenerationDateTime :: 'DateTimeType'(),
	thisDocumentIdentifier :: 'ProprietaryDocumentIdentifierType'(),
	toRole :: roleType()}).

So when I use a record in my elixir module (like the one below) I can’t use it.

Record.defrecord :Pip3A4PurchaseOrderRequestType, Record.extract(:Pip3A4PurchaseOrderRequestType, from: @hrl)
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.4.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> require Ot.Poc
iex(2)> record = Ot.Poc.Pip3A4PurchaseOrderRequestType()
** (SyntaxError) iex:2: syntax error before: '('

iex(2)> record = Ot.Poc.pip3A4PurchaseOrderRequestType()
** (UndefinedFunctionError) function Ot.Poc.pip3A4PurchaseOrderRequestType/0 is undefined or private. Did you mean one of:

      * Pip3A4PurchaseOrderRequestType/0
      * Pip3A4PurchaseOrderRequestType/1
      * Pip3A4PurchaseOrderRequestType/2

    (ot) Ot.Poc.pip3A4PurchaseOrderRequestType()

I thought I could fix this by defining the record as fallowed:

  #                 v--- lower cased p                              v--- Upper cased p
  Record.defrecord :pip3A4PurchaseOrderRequestType, Record.extract(:Pip3A4PurchaseOrderRequestType, from: @hrl)

Now I can call Ot.Poc.pip3A4PurchaseOrderRequestType(), but when I try to pass the record data it fails because the record links to other records that are starting with upper cased names, but first it fails because it can’t match it self with the data:

** (ArgumentError) expected argument to be a literal atom, literal keyword or a :pip3A4PurchaseOrderRequestType record, got runtime: {:Pip3A4PurchaseOrderRequestType, [], {:roleType, [],

So at this point I tried everything I could think off without success. What can I do without having to adjust the .erl file manually (392 LoC)?

Can you show the code that causes the ArgumentError and all code that defines the records?

Currently I’m failing to create a module which has a function beginning with a capital, but when I do Foo."Bar", elixir is at least complaining about the undefined function Foo.Bar/0.

Therefore I tend to conclude that quoting might help you.

Some more info:

defmodule Ot.Poc do
  def compile_xsd do
    {:ok, xsdModel} = 
      |> :erlsom.compile_xsd_file(include_dirs: [@xsdPath])


  def scan do
    {:ok, result, _} = :erlsom.scan_file(@xml, compile_xsd())

iex(3)> Ot.Poc.pip3A4PurchaseOrderRequestType(Ot.Poc.scan())
** (ArgumentError) expected argument to be a literal atom, literal keyword or a :pip3A4PurchaseOrderRequestType record, got runtime: {:Pip3A4PurchaseOrderRequestType, [], {:roleType, [], {:PartnerR
Type, [], {:ContactInformationType, [], {:FreeFormTextType, [], {:fftType, [], 'EN', 'Purchase Department'}}, '', {:CommunicationsNumberType, [], '+31 30 697 3289'}, {:Communic
pe, [], '+31 30 697 3288'}}, 'Reseller', {:PartnerDescriptionType, [], {:BusinessDescriptionType, [], '8714253023366', 'Information Technology', {:FreeFormTextType, [], {:fftType, [], 'EN', 'Onetra
 Buyer'}}}, 'Buyer'}}}, {:AuthenticationType, [], [], []}, 'Request', {:PurchaseOrderType, [], {:AccountDescriptionType, [], {:FreeFormTextType, [], {:fftType, [], 'EN', 'Onetrail B.V. - Test Buyer
23366', {:roleType, [], {:PartnerRoleDescriptionType, [], {:ContactInformationType, [], {:FreeFormTextType, [], {:fftType, [], 'EN', 'Finance Department'}}, '', {:Communications
, '+31 30 697 3289'}, {:CommunicationsNumberType, [], '+31 30 697 3288'}}, 'Reseller', {:PartnerDescriptionType, [], {:BusinessDescriptionType, [], '8714253023397', :undefined, {:FreeFormTextType,
 [], 'EN', 'Onetrail B.V. - Test Buyer'}}}, 'Buyer'}}}}, :undefined, {:RemarksType, [], [{:RemarkType, [], :undefined, 'Sales Remark', 'Header Text'}]}, :undefined, :undefined, ['Standard'], :undef
hippingInformationType, [], :undefined, ['AB - Pre Noon'], :undefined, ['Partial'], :undefined}, [{:ProductLineItemType, [], :undefined, {:RemarksType, [], [{:RemarkType, [], 'EN', :undefined, 'Lin
ndefined, 'EACH', :undefined, {:AffirmationIndicatorType, [], 'No'}, :undefined, '1', {:OrderQuantityType, [], {:requestedQuantityType, [], '28'}}, {:ProductIdentificationType, [], 'AP8853', [{:Par
ntificationType, [], 'Manufacturer', 'AP8853', :undefined}, {:PartnerProductIdentificationType, [], 'Seller', 'SellerPN1', :undefined}, {:PartnerProductIdentificationType, [], 'Buyer', 'BuyerPN1',
:PartnerProductIdentificationType, [], 'EAN', '8712345678921', :undefined}]}, {:FreeFormTextType, [], {:fftType, [], 'EN', 'Most Wonderful Product there is'}}, :undefined, [{:requestedEventType, []
tionEventType, [], '20150217T120000', 'Ship'}}], {:AmountType, [], {:FinancialAmountType, [], 'EUR', '316.96'}}, :undefined, {:AmountType, [], {:FinancialAmountType, [], 'EUR', '8874.88'}}}], {:Pro
ntIdentifierType, [], 'Standard WH 1'}, [{:requestedEventType, [], {:TransportationEventType, [], '20150217T120000', 'Ship'}}], :undefined, {:"PurchaseOrderType/shipTo", [], {:"ExtendedPartnerDescr
tnerDescription", [], {:BusinessDescriptionType, [], '8714253023380', :undefined, :undefined}, 'Buyer', {:ContactInformationType, [], {:FreeFormTextType, [], {:fftType, [], :undefined, :undefined}}
ed, {:CommunicationsNumberType, [], []}}}, :undefined}, {:AmountType, [], {:FinancialAmountType, [], 'EUR', '8874.88'}}, :undefined}, {:DateTimeType, [], '20150216T120000'}, {:ProprietaryDocumentId
[], 'ThisDocUniqueID-Counter993jfhsdf93n'}, {:roleType, [], {:PartnerRoleDescriptionType, [], {:ContactInformationType, [], {:FreeFormTextType, [], {:fftType, [], 'EN', 'Sales Department '}}, 'sale
', {:CommunicationsNumberType, [], '+31 30 697 3289'}, {:CommunicationsNumberType, [], '+31 30 697 3288'}}, 'Distributor', {:PartnerDescriptionType, [], {:BusinessDescriptionType, [], '871425302323
on Technology', {:FreeFormTextType, [], {:fftType, [], 'EN', 'Onetrail B.V. - Test Seller'}}}, 'Seller'}}}}
    (elixir) lib/record.ex:430: Record.__keyword__/3

The erl file and the result of :erlsom.scan_file(@xml, @compiled_xsd).

(All data are example data, but the structure is valid).

What about this approach:

require Record
defmodule Person do
  Record.defrecord :"Person", [:name, age: 25]


iex> require Person
iex> default = Person."Person"
{:Person, nil, 25}
iex> name_and_age = Person."Person"(name: "Bob Jones", age: 55)
{:Person, "Bob Jones", 55}

Example from Working with Erlang Records in Elixir

1 Like

Record.defrecord can accept an additional argument to differentiate the macro name from the record tag. And that’s probably what you want - lowercase macro with an uppercase tag.

1 Like

That looks right!

require Record
defmodule Person do
  Record.defrecord :person, :Person, [:name, age: 25]


iex> require Person                                            
iex> default = Person.person                                   
{:Person, nil, 25}
iex> name_and_age = Person.person(name: "Bob Jones", age: 55)  
{:Person, "Bob Jones", 55}

I got it working by doing the following:

defmodule Ot.Poc do
    for name <- Record.extract_all(from: @hrl) |> Keyword.keys do 
      Record.defrecord :"#{name |> to_string }", Record.extract(name, from: @hrl)
  # ...

BTW thanks @peerreynders! And the rest to of course! (quite surprised how fast I had my first reaction here :smiley:)