Skip to content

Commit a79d7ee

Browse files
committed
Decent optimizations for CssAttributeCollection
Realistic benchmark of MoveCssInline() Original: 1.2ms 592kB After: 1.0ms 482kB
1 parent efce60e commit a79d7ee

File tree

4 files changed

+55
-111
lines changed

4 files changed

+55
-111
lines changed

PreMailer.Net/PreMailer.Net.Tests/CssParserTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ public void AddStylesheet_ShouldKeepTheRightOrderOfCssAttributes()
188188

189189
var attributes = parser.Styles.First().Value.Attributes.ToArray();
190190

191-
Assert.True(attributes[0] is {Key: "background", Value: {Value: "red"}});
192-
Assert.True(attributes[1] is {Key: "background-color", Value: {Value: "green"}});
191+
Assert.True(attributes[0] is {Style: "background", Value: "red"});
192+
Assert.True(attributes[1] is {Style: "background-color", Value: "green" });
193193
}
194194

195195
[Fact]
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,28 @@
1-
using System;
2-
using System.Collections;
1+
using System.Collections;
32
using System.Collections.Generic;
4-
using System.Linq;
53

64
namespace PreMailer.Net {
7-
public class CssAttributeCollection : IDictionary<string, CssAttribute> {
8-
private readonly IDictionary<string, CssValue> _attributes;
9-
private int _currentPosition;
10-
11-
public CssAttributeCollection()
12-
{
13-
_attributes = new Dictionary<string, CssValue>(StringComparer.CurrentCultureIgnoreCase);
14-
_currentPosition = 0;
15-
}
16-
17-
/// <summary>
18-
/// Add a CssAttribute and set it's position to overwrite all previous CssAttributes in the same Collection.
19-
/// </summary>
20-
public void Add(KeyValuePair<string, CssAttribute> item)
21-
{
22-
_attributes.Add(item.Key, new CssValue(position: ++_currentPosition, attribute: item.Value));
23-
}
24-
25-
/// <summary>
26-
/// Add a CssAttribute and set it's position to overwrite all previous CssAttributes in the same Collection.
27-
/// </summary>
28-
public void Add(string key, CssAttribute value)
29-
{
30-
_attributes.Add(key, new CssValue(position: ++_currentPosition, attribute: value));
31-
}
5+
public class CssAttributeCollection : IEnumerable<CssAttribute> {
6+
private readonly List<CssAttribute> _attributes = new List<CssAttribute>();
327

338
/// <summary>
349
/// Add or Update a CssAttribute without changing it's position in the case of an update.
3510
/// </summary>
3611
public CssAttribute this[string key] {
37-
get => _attributes[key].Attribute;
12+
get {
13+
var index = IndexOfKey(key);
14+
return index == -1 ? null : _attributes[index];
15+
}
3816
set {
39-
if (_attributes.TryGetValue(key, out var existing)) {
40-
_attributes[key] = new CssValue(position: existing.Position, attribute: value);
17+
var index = IndexOfKey(key);
18+
if (index == -1)
19+
{
20+
_attributes.Add(value);
21+
4122
}
42-
else {
43-
_attributes[key] = new CssValue(position: ++_currentPosition, attribute: value);
23+
else
24+
{
25+
_attributes[index] = value;
4426
}
4527
}
4628
}
@@ -51,110 +33,72 @@ public CssAttribute this[string key] {
5133
public void Merge(CssAttribute attribute)
5234
{
5335
var key = attribute.Style;
54-
var value = attribute;
5536

56-
_attributes[key] = new CssValue(position: ++_currentPosition, attribute: value);
57-
}
37+
// Remove previous to instead append at the end
38+
Remove(key);
5839

59-
/// <summary>
60-
/// Copy the entries of this Collection, ordered by position, to the destination Array.
61-
/// </summary>
62-
public void CopyTo(KeyValuePair<string, CssAttribute>[] array, int arrayIndex)
63-
{
64-
var arr = _attributes.OrderBy(_ => _.Value.Position).Select(_ => new KeyValuePair<string, CssAttribute>(_.Key, _.Value.Attribute)).ToArray();
65-
Array.Copy(arr, 0, array, arrayIndex, arr.Length);
40+
this[key] = attribute;
6641
}
6742

6843
/// <summary>
6944
/// Gets an Enumerator of the attributes in this collection, ordered by position.
7045
/// </summary>
71-
public IEnumerator<KeyValuePair<string, CssAttribute>> GetEnumerator()
46+
public IEnumerator<CssAttribute> GetEnumerator()
7247
{
73-
return _attributes
74-
.OrderBy(_ => _.Value.Position)
75-
.Select(pair => new KeyValuePair<string, CssAttribute>(pair.Key, pair.Value.Attribute)).GetEnumerator();
48+
return _attributes.GetEnumerator();
7649
}
7750

78-
/// <summary>
79-
/// Gets an Enumerator of the attributes in this collection, ordered by position.
80-
/// </summary>
8151
IEnumerator IEnumerable.GetEnumerator()
8252
{
83-
return GetEnumerator();
53+
return _attributes.GetEnumerator();
8454
}
8555

86-
/// <summary>
87-
/// Gets all Keys in this Collection ordered by position.
88-
/// </summary>
89-
public ICollection<string> Keys => _attributes.OrderBy(_ => _.Value.Position).Select(_ => _.Key).ToList();
90-
91-
/// <summary>
92-
/// Gets all Values in this Collection ordered by position.
93-
/// </summary>
94-
public ICollection<CssAttribute> Values => _attributes.Values.OrderBy(_ => _.Position).Select(_ => _.Attribute).ToList();
95-
96-
/// <inheritdoc />
9756
public int Count => _attributes.Count;
9857

99-
/// <inheritdoc />
100-
public bool IsReadOnly => _attributes.IsReadOnly;
101-
102-
/// <inheritdoc />
10358
public bool TryGetValue(string key, out CssAttribute value)
10459
{
105-
if (_attributes.TryGetValue(key, out var data)) {
106-
value = data.Attribute;
60+
var index = IndexOfKey(key);
61+
if (index != -1)
62+
{
63+
value = _attributes[index];
10764
return true;
10865
}
10966

11067
value = default;
11168
return false;
11269
}
11370

114-
/// <inheritdoc />
115-
public bool Contains(KeyValuePair<string, CssAttribute> item)
71+
private int IndexOfKey(string key)
11672
{
117-
return _attributes.TryGetValue(item.Key, out var value) && value.Attribute == item.Value;
118-
}
73+
for (int i = 0; i < _attributes.Count; i++)
74+
{
75+
var attribute = _attributes[i];
76+
if (string.Equals(attribute.Style, key, System.StringComparison.OrdinalIgnoreCase))
77+
{
78+
return i;
79+
}
80+
}
11981

120-
/// <inheritdoc />
121-
public bool ContainsKey(string key)
122-
{
123-
return _attributes.ContainsKey(key);
82+
return -1;
12483
}
12584

126-
/// <inheritdoc />
127-
public bool Remove(string key)
85+
public bool ContainsKey(string key)
12886
{
129-
return _attributes.Remove(key);
87+
return IndexOfKey(key) != -1;
13088
}
13189

132-
/// <inheritdoc />
133-
public bool Remove(KeyValuePair<string, CssAttribute> item)
90+
public void Remove(string key)
13491
{
135-
if (_attributes.TryGetValue(item.Key, out var value) && value.Attribute == item.Value) {
136-
return _attributes.Remove(new KeyValuePair<string, CssValue>(item.Key, value));
92+
var index = IndexOfKey(key);
93+
if (index != -1)
94+
{
95+
_attributes.RemoveAt(index);
13796
}
138-
139-
return false;
14097
}
141-
142-
/// <inheritdoc />
98+
14399
public void Clear()
144100
{
145101
_attributes.Clear();
146102
}
147-
148-
149-
private readonly struct CssValue {
150-
public int Position { get; }
151-
public CssAttribute Attribute { get; }
152-
153-
public CssValue(int position, CssAttribute attribute)
154-
{
155-
Position = position;
156-
Attribute = attribute;
157-
}
158-
}
159103
}
160104
}

PreMailer.Net/PreMailer.Net/CssElementStyleResolver.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@ private static void AddSpecialPremailerAttributes(List<AttributeToCss> attribute
2626
{
2727
while (true)
2828
{
29-
var premailerRuleMatch = styleClass.Attributes.FirstOrDefault(a => a.Key.StartsWith(premailerAttributePrefix));
29+
var premailerRuleMatch = styleClass.Attributes.FirstOrDefault(a => a.Style.StartsWith(premailerAttributePrefix));
3030

31-
var key = premailerRuleMatch.Key;
32-
var cssAttribute = premailerRuleMatch.Value;
33-
34-
if (key == null)
31+
if (premailerRuleMatch == null)
3532
break;
3633

34+
var key = premailerRuleMatch.Style;
35+
var cssAttribute = premailerRuleMatch.Value;
36+
3737
attributeCssList.Add(new AttributeToCss
3838
{
3939
AttributeName = key.Replace(premailerAttributePrefix, ""),
40-
CssValue = cssAttribute.Value
40+
CssValue = cssAttribute
4141
});
4242

4343
styleClass.Attributes.Remove(key);

PreMailer.Net/PreMailer.Net/StyleClass.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ public StyleClass()
3636
/// <param name="canOverwrite">if set to <c>true</c> [can overwrite].</param>
3737
public void Merge(StyleClass styleClass, bool canOverwrite) {
3838
foreach (var item in styleClass.Attributes) {
39-
if (!Attributes.TryGetValue(item.Key, out var existing) ||
40-
canOverwrite && (!existing.Important || item.Value.Important))
39+
if (!Attributes.TryGetValue(item.Style, out var existing) ||
40+
canOverwrite && (!existing.Important || item.Important))
4141
{
42-
Attributes.Merge(item.Value);
42+
Attributes.Merge(item);
4343
}
4444
}
4545
}
@@ -55,7 +55,7 @@ public override string ToString() {
5555
/// <param name="emitImportant">When set to <c>true</c>, resulting CSS emits the !important flag.</param>
5656
/// <returns> css styles with or without !important </returns>
5757
public string ToString(bool emitImportant) {
58-
return string.Join(";", Attributes.Values.Select(_ => _.ToString(emitImportant)));
58+
return string.Join(";", Attributes.Select(_ => _.ToString(emitImportant)));
5959
}
6060
}
6161
}

0 commit comments

Comments
 (0)