Skip to content

Commit

Permalink
Fix DuplicateAnchorException when using merge keys do merge mappings …
Browse files Browse the repository at this point in the history
…that contain aliases.

Implement merging a sequence of aliases.
  • Loading branch information
aaubry committed Aug 8, 2014
1 parent 8c5a955 commit 63d4315
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 2 deletions.
80 changes: 80 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,86 @@ public void BackreferencesAreMergedWithMappings()
.And.Contain("key3", "value3", "key3 is defined in the actual mapping");
}

[Fact]
public void MergingDoesNotProduceDuplicateAnchors()
{
var parser = new MergingParser(Yaml.ParserForText(@"
anchor: &default
key1: &myValue value1
key2: value2
alias:
<<: *default
key2: Overriding key2
key3: value3
useMyValue:
key: *myValue
"));
var result = Deserializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(new EventReader(parser));

var alias = result["alias"];
alias.Should()
.Contain("key1", "value1", "key1 should be inherited from the backreferenced mapping")
.And.Contain("key2", "Overriding key2", "key2 should be overriden by the actual mapping")
.And.Contain("key3", "value3", "key3 is defined in the actual mapping");

result["useMyValue"].Should()
.Contain("key", "value1", "key should be copied");
}

[Fact]
public void ExampleFromSpecificationIsHandledCorrectly()
{
var parser = new MergingParser(Yaml.ParserForText(@"
obj:
- &CENTER { x: 1, y: 2 }
- &LEFT { x: 0, y: 2 }
- &BIG { r: 10 }
- &SMALL { r: 1 }
# All the following maps are equal:
results:
- # Explicit keys
x: 1
y: 2
r: 10
label: center/big
- # Merge one map
<< : *CENTER
r: 10
label: center/big
- # Merge multiple maps
<< : [ *CENTER, *BIG ]
label: center/big
- # Override
#<< : [ *BIG, *LEFT, *SMALL ] # This does not work because, in the current implementation,
# later keys override former keys. This could be fixed, but that
# is not trivial because the deserializer allows aliases to refer to
# an anchor that is defined later in the document, and the way it is
# implemented, the value is assigned later when the anchored value is
# deserialized.
<< : [ *SMALL, *LEFT, *BIG ]
x: 1
label: center/big
"));

var result = Deserializer.Deserialize<Dictionary<string, List<Dictionary<string, string>>>>(new EventReader(parser));

int index = 0;
foreach (var mapping in result["results"])
{
mapping.Should()
.Contain("x", "1", "'x' should be '1' in result #{0}", index)
.And.Contain("y", "2", "'y' should be '2' in result #{0}", index)
.And.Contain("r", "10", "'r' should be '10' in result #{0}", index)
.And.Contain("label", "center/big", "'label' should be 'center/big' in result #{0}", index);

++index;
}
}

[Fact]
public void IgnoreExtraPropertiesIfWanted()
{
Expand Down
2 changes: 1 addition & 1 deletion YamlDotNet.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- This build file requires .NET 4.0 and nant 0.91 alpha 2 or higher -->
<property name="nant.settings.currentframework" value="net-4.0"/>

<property name="version" value="3.2.1" />
<property name="version" value="3.2.2" />

<fileset id="binaries">
<include name="YamlDotNet/bin/Release/YamlDotNet.dll" />
Expand Down
103 changes: 102 additions & 1 deletion YamlDotNet/Core/MergingParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using YamlDotNet.Core.Events;

Expand Down Expand Up @@ -40,8 +41,39 @@ public bool MoveNext()
var mergedEvents = GetMappingEvents(anchorAlias.Value);
_allEvents.RemoveRange(i, 2);
_allEvents.InsertRange(i, mergedEvents);
continue;
}

var sequence = _allEvents[i + 1] as SequenceStart;
if (sequence != null)
{
var mergedEvents = new List<IEnumerable<ParsingEvent>>();
var sequenceEndFound = false;
for (var itemIndex = i + 2; itemIndex < _allEvents.Count; ++itemIndex)
{
anchorAlias = _allEvents[itemIndex] as AnchorAlias;
if (anchorAlias != null)
{
mergedEvents.Add(GetMappingEvents(anchorAlias.Value));
continue;
}

if (_allEvents[itemIndex] is SequenceEnd)
{
_allEvents.RemoveRange(i, itemIndex - i + 1);
_allEvents.InsertRange(i, mergedEvents.SelectMany(e => e));
sequenceEndFound = true;
break;
}
}

if (sequenceEndFound)
{
continue;
}
}

throw new SemanticErrorException(merge.Start, merge.End, "Unrecognized merge key pattern");
}
}
}
Expand All @@ -58,6 +90,8 @@ public bool MoveNext()

private IEnumerable<ParsingEvent> GetMappingEvents(string mappingAlias)
{
var cloner = new ParsingEventCloner();

var nesting = 0;
return _allEvents
.SkipWhile(e =>
Expand All @@ -67,7 +101,74 @@ private IEnumerable<ParsingEvent> GetMappingEvents(string mappingAlias)
})
.Skip(1)
.TakeWhile(e => (nesting += e.NestingIncrease) >= 0)
.Select(e => cloner.Clone(e))
.ToList();
}

private class ParsingEventCloner : IParsingEventVisitor
{
private ParsingEvent clonedEvent;

public ParsingEvent Clone(ParsingEvent e)
{
e.Accept(this);
return clonedEvent;
}

void IParsingEventVisitor.Visit(AnchorAlias e)
{
clonedEvent = new AnchorAlias(e.Value, e.Start, e.End);
}

void IParsingEventVisitor.Visit(StreamStart e)
{
throw new NotSupportedException();
}

void IParsingEventVisitor.Visit(StreamEnd e)
{
throw new NotSupportedException();
}

void IParsingEventVisitor.Visit(DocumentStart e)
{
throw new NotSupportedException();
}

void IParsingEventVisitor.Visit(DocumentEnd e)
{
throw new NotSupportedException();
}

void IParsingEventVisitor.Visit(Scalar e)
{
clonedEvent = new Scalar(null, e.Tag, e.Value, e.Style, e.IsPlainImplicit, e.IsQuotedImplicit, e.Start, e.End);
}

void IParsingEventVisitor.Visit(SequenceStart e)
{
clonedEvent = new SequenceStart(null, e.Tag, e.IsImplicit, e.Style, e.Start, e.End);
}

void IParsingEventVisitor.Visit(SequenceEnd e)
{
clonedEvent = new SequenceEnd(e.Start, e.End);
}

void IParsingEventVisitor.Visit(MappingStart e)
{
clonedEvent = new MappingStart(null, e.Tag, e.IsImplicit, e.Style, e.Start, e.End);
}

void IParsingEventVisitor.Visit(MappingEnd e)
{
clonedEvent = new MappingEnd(e.Start, e.End);
}

void IParsingEventVisitor.Visit(Comment e)
{
throw new NotSupportedException();
}
}
}
}

0 comments on commit 63d4315

Please sign in to comment.