Skip to content

Commit

Permalink
improved handling of nil values. (Not 100% backward compatible.)
Browse files Browse the repository at this point in the history
Note: this changes the output of erlsom:scan() in case there are nil elements (xsi:nil=true) in the input.
  • Loading branch information
willemdj committed Oct 9, 2011
1 parent 549a383 commit bc48604
Show file tree
Hide file tree
Showing 7 changed files with 1,505 additions and 1,043 deletions.
2,322 changes: 1,347 additions & 975 deletions doc/erlsom.htm

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/erlsom_compile.erl
Expand Up @@ -726,9 +726,9 @@ translateSequenceInSequence(Sequence = #sequenceType{minOccurs=Min, maxOccurs=Ma

%% -record(elementInfo, {alternatives, min, max}).
%% -record(alternative, {tag, type, real}).
translateLocalElement(Element = #localElementType{minOccurs=Min, maxOccurs = Max}, Acc) ->
translateLocalElement(Element = #localElementType{minOccurs=Min, maxOccurs = Max, nillable=Nillable}, Acc) ->
{Alternative, Acc2} = translateQuasiAlternative(Element, Acc),
{#elementInfo{alternatives= [Alternative], min=minMax(Min), max=minMax(Max)}, Acc2}.
{#elementInfo{alternatives= [Alternative], min=minMax(Min), max=minMax(Max), nillable=Nillable}, Acc2}.

translateAlternative(#localElementType{type=undefined, ref=Ref, simpleOrComplex=undefined,
minOccurs=Min, maxOccurs=Max}, Acc = #p1acc{nss = Nss}) when
Expand Down
2 changes: 1 addition & 1 deletion src/erlsom_compile.hrl
Expand Up @@ -85,7 +85,7 @@
min = 1,
max = 1}).

-record(elementInfo, {alternatives, min = 1, max = 1}).
-record(elementInfo, {alternatives, min = 1, max = 1, nillable}).
-record(alternative, {tag, type, real, min = 1, max = 1, anyInfo}).
-record(attrib, {name, optional, type, ref}).
-record(attGrp, {name, atts, anyAttr}).
101 changes: 66 additions & 35 deletions src/erlsom_parse.erl
Expand Up @@ -175,6 +175,9 @@
-import(erlsom_lib, [findType/6]).
-import(erlsom_lib, [convertPCData/4]).

-define(format_record(Rec, Name),
user_default:format_record(Rec, Name, record_info(fields, Name))).

%%%% lots of stuff to help debugging. Most of it only produces output if the process variable 'erlsom_debug'
%%%% is set. This makes it possible to suppress this output in parts of the test program that use erlsom for parsing
%%%% of a big configuration file.
Expand Down Expand Up @@ -376,8 +379,8 @@ stateMachine(Event,
%% process the attributes
if
RealElement ->
NewRecord = processAttributes(AttributeValues, TheType, newRecord(TheType),
State#state.model, Namespaces);
{NewRecord, _} = processAttributes(AttributeValues, TheType, newRecord(TheType),
State#state.model, Namespaces, false);
true ->
NewRecord = newRecord(TheType)
end,
Expand Down Expand Up @@ -515,8 +518,8 @@ stateMachine(Event, State = #state{currentState = #altState{name=Name, type=Type
%% process the attributes
if
Real ->
NewRecord = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces);
{NewRecord, _} = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces, false);
true ->
NewRecord = newRecord(TypeDef)
end,
Expand Down Expand Up @@ -714,7 +717,8 @@ stateMachine(Event, State = #state{currentState = #cs{re = RemainingElements,
%% debugEvent(Event),
[#el{alts = Alternatives,
mn = MinOccurs,
mx = MaxOccurs} | NextElements] = RemainingElements,
mx = MaxOccurs,
nillable = Nullable} | NextElements] = RemainingElements,
case Event of
{startElement, Uri, LocalName, _Prefix, Attributes} ->
Name = eventName(LocalName, Uri, NamespaceMapping),
Expand Down Expand Up @@ -749,8 +753,14 @@ stateMachine(Event, State = #state{currentState = #cs{re = RemainingElements,
{'#PCDATA', PCDataType} ->
%% debug("receive text events"),
%% push the current status, create a new level in the state machine
State#state{currentState = {'#PCDATA', PCDataType, []},
resultSoFar = [State#state.currentState | ResultSoFar]};
case (Nullable==true) andalso findNullableAttribute(Attributes) of
true ->
State#state{currentState = #cs{re = [], sf =0, er = nil, rl = true, mxd = false},
resultSoFar = [State#state.currentState | ResultSoFar]};
_ ->
State#state{currentState = {'#PCDATA', PCDataType, []},
resultSoFar = [State#state.currentState | ResultSoFar]}
end;
_Else ->
%% not text: a complex type.
%% look for the type discription
Expand All @@ -767,17 +777,25 @@ stateMachine(Event, State = #state{currentState = #cs{re = RemainingElements,
%% process the attributes
if
RealElement2 ->
NewRecord = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces);
{NewRecord, Nil} = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces, Nullable);
true ->
NewRecord = newRecord(TypeDef)
{NewRecord, Nil} = {newRecord(TypeDef), false}
end,
case Tp of
sequence ->
NewCurrentState =
if
Nil ->
%% Just need to get a closing tag (so no elements (re) below this one)
%% The value will be a record as usual - xsi:nil = true will be in
%% anyAttributes, and elements will be undefined. TODO: change this
%% into something that clearly indicates a nil value, but still allows for
%% attributes...
#cs{re = [], sf =0, er = {nil, NewRecord}, rl = RealElement2, mxd = NewMxd};
Tp == sequence ->
%% debug(NewRecord),
NewCurrentState = #cs{re = Elements, sf =0, er = NewRecord, rl = RealElement2, mxd = NewMxd};
all ->
NewCurrentState = #all{re = Elements, er = NewRecord}
#cs{re = Elements, sf =0, er = NewRecord, rl = RealElement2, mxd = NewMxd};
Tp == all ->
#all{re = Elements, er = NewRecord}
end,
%% push the current status, create a new level in the state machine
NewState = State#state{currentState = NewCurrentState,
Expand Down Expand Up @@ -1001,8 +1019,8 @@ stateMachine(Event, State = #state{currentState = #all{re = RemainingElements,
%% process the attributes
if
RealElement ->
NewRecord = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces);
{NewRecord, _} = processAttributes(Attributes, TypeDef, newRecord(TypeDef),
State#state.model, Namespaces, false);
true ->
NewRecord = newRecord(TypeDef)
end,
Expand Down Expand Up @@ -1182,7 +1200,8 @@ specialEventName(LocalName, Uri) ->
%% Record: a record of right type for the element
%%
%% Returns:
%% updated version of the Record.
%% {updated version of the Record, Null} where Null = true if nillable is true and an xsi:nil
%% attribute is found, false otherwise.
%%
%% Exceptions:
%% throws an error if attributes where missing, or if an unexpected attribute is found
Expand All @@ -1191,21 +1210,21 @@ specialEventName(LocalName, Uri) ->
%% Process attributes one by one, remove processed attributes from the
%% ListOfAttributes, and, finally, see whether there are any non-optional
%% attributes left in the ListOfAttributes
processAttributes(Attributes, Type = #type{atts = ToReceive}, Record, Model, Namespaces) ->
processAttributes(Attributes, ToReceive, Type, Record, Model, Namespaces).
processAttributes(Attributes, Type = #type{atts = ToReceive}, Record, Model, Namespaces, Nullable) ->
processAttributes(Attributes, ToReceive, Type, Record, Model, Namespaces, Nullable, false).

processAttributes(_Attributes = [], ToReceive, _Type, Record, _Model, _Namespaces) ->
processAttributes(_Attributes = [], ToReceive, _Type, Record, _Model, _Namespaces, _Nullable, Nil) ->
checkAttributePresence(ToReceive),
%% case catch checkAttributePresence(ToReceive) of
%% true -> ok;
%% _ ->
%% debug(Type),
%% throw({error, "remove me"})
%% end,
Record;
{Record, Nil};

processAttributes(_Attributes = [#attribute{localName=LocalName, uri=Uri, value=Value} | Tail], ListOfAttributes,
TypeDef, Record, Model = #model{nss = NamespaceMapping}, Namespaces) ->
TypeDef, Record, Model = #model{nss = NamespaceMapping}, Namespaces, Nullable, Nil) ->
Name = eventName(LocalName, Uri, NamespaceMapping),
case lists:keysearch(Name, #att.nm, ListOfAttributes) of
{value, #att{nr = Position, tp = Type}} ->
Expand All @@ -1216,7 +1235,7 @@ processAttributes(_Attributes = [#attribute{localName=LocalName, uri=Uri, value=
end,
processAttributes(Tail, lists:keydelete(Name, #att.nm, ListOfAttributes), TypeDef,
setelement(Position + 2, Record, ConvertedValue),
Model, Namespaces);
Model, Namespaces, Nullable, Nil);
_Else ->
%% debug(Name),
ConvertedValue = try convertPCData(Value, char, Namespaces, NamespaceMapping)
Expand All @@ -1225,29 +1244,29 @@ processAttributes(_Attributes = [#attribute{localName=LocalName, uri=Uri, value=
throw({error, "Wrong Type in value for attribute " ++ LocalName})
end,
%% see whether the attribute is 'special'
case {LocalName, Uri} of
Nil2 = case {LocalName, Uri} of
{"nil", "http://www.w3.org/2001/XMLSchema-instance"} ->
%% see whether the element is 'nillable'
case TypeDef#type.nillable of
case Nullable of
true ->
ok;
attributeIsTrue(ConvertedValue);
_ ->
throw({error, "Unexpected attribute: " ++ LocalName})
end;

{"schemaLocation", "http://www.w3.org/2001/XMLSchema-instance"} ->
ok;
Nil;

%% TODO: do something with this!!!
{"space", "http://www.w3.org/XML/1998/namespace"} ->
%% debug("got a space attrib"),
ok;
Nil;

{"noNamespaceSchemaLocation", "http://www.w3.org/2001/XMLSchema-instance"} ->
ok;
Nil;

{"type", "http://www.w3.org/2001/XMLSchema-instance"} ->
ok;
Nil;

_NotSpecial ->
#type{anyAttr = AnyAttr} = TypeDef,
Expand All @@ -1267,7 +1286,7 @@ processAttributes(_Attributes = [#attribute{localName=LocalName, uri=Uri, value=
Uri /= [] ->
throw({error, "Unexpected attribute: " ++ LocalName});
true ->
ok
Nil
end;
"##other"->
%% debug(Uri),
Expand All @@ -1277,16 +1296,17 @@ processAttributes(_Attributes = [#attribute{localName=LocalName, uri=Uri, value=
Uri == Tns ->
throw({error, "Unexpected attribute (from target NS): " ++ LocalName});
true ->
ok
Nil
end;
_ -> %% ##any, or a list of namespaces - the list is not checked
ok
Nil
end
end
end,

ListOfAttributes2 = [{{LocalName, Uri}, ConvertedValue} | element(2, Record)],
processAttributes(Tail, ListOfAttributes, TypeDef, setelement(2, Record, ListOfAttributes2), Model, Namespaces)
processAttributes(Tail, ListOfAttributes, TypeDef, setelement(2, Record, ListOfAttributes2), Model, Namespaces,
Nullable, Nil2)
end.

%% Goal:
Expand Down Expand Up @@ -1404,3 +1424,14 @@ matchesAnyInfo(Uri, #anyInfo{ns = Namespace, tns = Tns}, _) ->
%% debug("TODO!!"),
true
end.

findNullableAttribute([]) ->
false;
findNullableAttribute([#attribute{localName="nil", uri="http://www.w3.org/2001/XMLSchema-instance", value=Value} | _]) ->
attributeIsTrue(Value);
findNullableAttribute([_Att | Tail]) ->
findNullableAttribute(Tail).

attributeIsTrue("true") -> true;
attributeIsTrue("1") -> true;
attributeIsTrue(_) -> false.
2 changes: 1 addition & 1 deletion src/erlsom_parse.hrl
Expand Up @@ -37,7 +37,7 @@
%% for derived types. The 'nm' field is actually a key, which may
%% include an additional prefix to differntiate between elements, types
%% and groups.
-record(el, {alts, mn = 1, mx = 1, nr}).
-record(el, {alts, mn = 1, mx = 1, nillable, nr}).
-record(alt, {tag, tp, nxt = [], mn = 1, mx = 1, rl = true, anyInfo}).
-record(att, {nm, nr, opt, tp}).
%% -record(ns, {uri, pf}).
Expand Down
6 changes: 3 additions & 3 deletions src/erlsom_pass2.erl
Expand Up @@ -48,7 +48,7 @@
%% xml document.
%% -record(typeInfo,
%% {typeName, global, typeType, typeRef, elements, attributes}).
%% -record(elementInfo, {alternatives, min, max}).
%% -record(elementInfo, {alternatives, min, max, nillable}).
%% -record(alternative, {tag, type, real, min, max}).
%% -record(attribute, {name, optional, type}).
%% -record(schemaInfo, {targetNamespace, elementFormDefault, namespacePrefix, namespaces, path=[]}).
Expand Down Expand Up @@ -365,10 +365,10 @@ translateElements([], Acc, _SeqNr, _Types) ->
%% value is redundant, it is only there because I thought it would be easier,
%% and maybe more performant.

translateElement(#elementInfo{alternatives=Alternatives, min=Min, max=Max}, SeqNr, Types) ->
translateElement(#elementInfo{alternatives=Alternatives, min=Min, max=Max, nillable=Nillable}, SeqNr, Types) ->
#el{alts = translateAlternatives(Alternatives, [], Types),
mn = Min,
mx = Max, nr = SeqNr}.
mx = Max, nr = SeqNr, nillable=Nillable}.

translateAlternatives([Alternative | Tail], Acc, Types) ->
translateAlternatives(Tail, [translateAlternative(Alternative, Types) | Acc], Types);
Expand Down

0 comments on commit bc48604

Please sign in to comment.