Skip to content

Commit 01ff789

Browse files
authored
Merge pull request #222 from mlaily/allow-anchor-overwriting
Allow anchors redefinition/overwriting
2 parents 4b87935 + 71a793d commit 01ff789

File tree

9 files changed

+74
-113
lines changed

9 files changed

+74
-113
lines changed

YamlDotNet.Test/RepresentationModel/YamlStreamTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ public void Roundtrip32BitsUnicodeEscape()
204204
{
205205
RoundtripTest("unicode-32bits-escape.yaml");
206206
}
207+
208+
[Fact]
209+
public void AnchorsOverwriting()
210+
{
211+
RoundtripTest("anchors-overwriting.yaml");
212+
}
207213

208214
[Fact]
209215
public void AllAliasesMustBeResolved()

YamlDotNet.Test/Serialization/SerializationTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,24 @@ public void DeserializationOfInt64Succeeds(long value)
13711371
Assert.Equal(value, parsed);
13721372
}
13731373

1374+
public class AnchorsOverwritingTestCase
1375+
{
1376+
public List<string> a { get; set; }
1377+
public List<string> b { get; set; }
1378+
public List<string> c { get; set; }
1379+
public List<string> d { get; set; }
1380+
}
1381+
1382+
[Fact]
1383+
public void DeserializationOfStreamWithDuplicateAnchorsSucceeds()
1384+
{
1385+
var yaml = Yaml.ParserForResource("anchors-overwriting.yaml");
1386+
var serializer = new DeserializerBuilder()
1387+
.IgnoreUnmatchedProperties()
1388+
.Build();
1389+
var deserialized = serializer.Deserialize<AnchorsOverwritingTestCase>(yaml);
1390+
}
1391+
13741392
[Fact]
13751393
public void SerializeExceptionWithStackTrace()
13761394
{

YamlDotNet.Test/YamlDotNet.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
<EmbeddedResource Include="files\ordered-properties.yaml" />
142142
<EmbeddedResource Include="files\multi-doc-tag.yaml" />
143143
<EmbeddedResource Include="files\unicode-32bits-escape.yaml" />
144+
<EmbeddedResource Include="files\anchors-overwriting.yaml" />
144145
<None Include="packages.config" />
145146
</ItemGroup>
146147
<ItemGroup>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
a : &anchor [foo]
2+
c : *anchor
3+
b : &anchor [bar]
4+
d : *anchor
5+
6+
e : &anchor2 baz
7+
g : *anchor2
8+
f : &anchor2 foobar
9+
h : *anchor2

YamlDotNet/Core/DuplicateAnchorException.cs

Lines changed: 0 additions & 81 deletions
This file was deleted.

YamlDotNet/RepresentationModel/DocumentLoadingState.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ public void AddAnchor(YamlNode node)
4747

4848
if (anchors.ContainsKey(node.Anchor))
4949
{
50-
throw new DuplicateAnchorException(node.Start, node.End, string.Format(CultureInfo.InvariantCulture, "The anchor '{0}' already exists", node.Anchor));
50+
anchors[node.Anchor] = node;
51+
}
52+
else
53+
{
54+
anchors.Add(node.Anchor, node);
5155
}
52-
53-
anchors.Add(node.Anchor, node);
5456
}
5557

5658
/// <summary>

YamlDotNet/RepresentationModel/YamlDocument.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,15 @@ internal YamlDocument(IParser parser)
8282
}
8383

8484
/// <summary>
85-
/// Visitor that assigns anchors to nodes that are referenced more than once but have no anchor.
85+
/// Visitor that assigns anchors to nodes that are referenced more than once.
86+
/// Existing anchors are preserved as much as possible.
8687
/// </summary>
8788
private class AnchorAssigningVisitor : YamlVisitorBase
8889
{
8990
private readonly HashSet<string> existingAnchors = new HashSet<string>();
91+
/// <summary>
92+
/// Key: Node, Value: IsDuplicate
93+
/// </summary>
9094
private readonly Dictionary<YamlNode, bool> visitedNodes = new Dictionary<YamlNode, bool>();
9195

9296
public void AssignAnchors(YamlDocument document)
@@ -102,55 +106,61 @@ public void AssignAnchors(YamlDocument document)
102106
if (visitedNode.Value)
103107
{
104108
string anchor;
105-
do
109+
// If the existing anchor is not already used, we can have it
110+
if (!string.IsNullOrEmpty(visitedNode.Key.Anchor) && !existingAnchors.Contains(visitedNode.Key.Anchor))
106111
{
107-
anchor = random.Next().ToString(CultureInfo.InvariantCulture);
108-
} while (existingAnchors.Contains(anchor));
109-
existingAnchors.Add(anchor);
112+
anchor = visitedNode.Key.Anchor;
113+
}
114+
else
115+
{
116+
do
117+
{
118+
anchor = random.Next().ToString(CultureInfo.InvariantCulture);
119+
} while (existingAnchors.Contains(anchor));
120+
}
110121

122+
existingAnchors.Add(anchor);
111123
visitedNode.Key.Anchor = anchor;
112124
}
113125
}
114126
}
115127

116-
private void VisitNode(YamlNode node)
128+
/// <summary>
129+
/// Returns whether the visited node is a duplicate.
130+
/// </summary>
131+
private bool VisitNodeAndFindDuplicates(YamlNode node)
117132
{
118-
if (string.IsNullOrEmpty(node.Anchor))
133+
bool isDuplicate;
134+
if (visitedNodes.TryGetValue(node, out isDuplicate))
119135
{
120-
bool isDuplicate;
121-
if (visitedNodes.TryGetValue(node, out isDuplicate))
122-
{
123-
if (!isDuplicate)
124-
{
125-
visitedNodes[node] = true;
126-
}
127-
}
128-
else
136+
if (!isDuplicate)
129137
{
130-
visitedNodes.Add(node, false);
138+
visitedNodes[node] = true;
131139
}
140+
return !isDuplicate;
132141
}
133142
else
134143
{
135-
existingAnchors.Add(node.Anchor);
144+
visitedNodes.Add(node, false);
145+
return false;
136146
}
137147
}
138148

139149
public override void Visit(YamlScalarNode scalar)
140150
{
141-
// Do not assign anchors to scalars
151+
VisitNodeAndFindDuplicates(scalar);
142152
}
143153

144154
public override void Visit(YamlMappingNode mapping)
145155
{
146-
VisitNode(mapping);
147-
base.Visit(mapping);
156+
if (!VisitNodeAndFindDuplicates(mapping))
157+
base.Visit(mapping);
148158
}
149159

150160
public override void Visit(YamlSequenceNode sequence)
151161
{
152-
VisitNode(sequence);
153-
base.Visit(sequence);
162+
if (!VisitNodeAndFindDuplicates(sequence))
163+
base.Visit(sequence);
154164
}
155165
}
156166

YamlDotNet/Serialization/ValueDeserializers/AliasValueDeserializer.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public object Value
106106
}
107107
}
108108

109-
public object DeserializeValue (IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer)
109+
public object DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer)
110110
{
111111
object value;
112112
var alias = parser.Allow<AnchorAlias>();
@@ -148,10 +148,7 @@ public object DeserializeValue (IParser parser, Type expectedType, SerializerSta
148148
}
149149
else
150150
{
151-
throw new DuplicateAnchorException(nodeEvent.Start, nodeEvent.End, string.Format(
152-
"Anchor '{0}' already defined",
153-
anchor
154-
));
151+
aliasState[anchor] = new ValuePromise(value);
155152
}
156153
}
157154

YamlDotNet/YamlDotNet.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@
148148
<Compile Include="Core\CharacterAnalyzer.cs" />
149149
<Compile Include="Core\Constants.cs" />
150150
<Compile Include="Core\Cursor.cs" />
151-
<Compile Include="Core\DuplicateAnchorException.cs" />
152151
<Compile Include="Core\Emitter.cs" />
153152
<Compile Include="Core\EmitterState.cs" />
154153
<Compile Include="Core\ParserExtensions.cs" />

0 commit comments

Comments
 (0)