Skip to content

Commit cf48977

Browse files
Fix for #575
1 parent 973972a commit cf48977

File tree

4 files changed

+194
-4
lines changed

4 files changed

+194
-4
lines changed

src/HtmlAgilityPack.Shared/HtmlAttribute.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ public enum AttributeValueQuote
355355
/// <summary>
356356
/// The initial value (current value)
357357
/// </summary>
358-
Initial
358+
Initial,
359+
360+
/// <summary>
361+
/// The initial value (current value). However tag without value will have a double quote by default
362+
/// </summary>
363+
InitialExceptWithoutValue
359364
}
360365
}

src/HtmlAgilityPack.Shared/HtmlNode.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,7 +1752,7 @@ public void RemoveAllIDforNode(HtmlNode node)
17521752
}
17531753
}
17541754

1755-
/// <summary>Move a node already associated and append it to this node instead.</summary>
1755+
/// <summary>Move a node already associated and append it to this node instead (must be from a different document).</summary>
17561756
/// <param name="child">The child node to move.</param>
17571757
public void MoveChild(HtmlNode child)
17581758
{
@@ -1771,7 +1771,7 @@ public void MoveChild(HtmlNode child)
17711771
}
17721772
}
17731773

1774-
/// <summary>Move a children collection already associated and append it to this node instead.</summary>
1774+
/// <summary>Move a children collection already associated and append it to this node instead (must be from a different document).</summary>
17751775
/// <param name="children">The children collection already associated to move to another node.</param>
17761776
public void MoveChildren(HtmlNodeCollection children)
17771777
{
@@ -2411,6 +2411,11 @@ internal void WriteAttribute(TextWriter outText, HtmlAttribute att)
24112411
{
24122412
quoteType = att.QuoteType;
24132413
}
2414+
else if(quoteType == AttributeValueQuote.InitialExceptWithoutValue)
2415+
{
2416+
// if the quote doesn't have value, use double quote (https://github.com/zzzprojects/html-agility-pack/issues/575)
2417+
quoteType = att.QuoteType == AttributeValueQuote.WithoutValue ? AttributeValueQuote.DoubleQuote : att.QuoteType;
2418+
}
24142419

24152420
var isWithoutValue = quoteType == AttributeValueQuote.WithoutValue;
24162421

@@ -2460,7 +2465,7 @@ internal void WriteAttribute(TextWriter outText, HtmlAttribute att)
24602465
if (!isWithoutValue)
24612466
{
24622467
var value = quoteType == AttributeValueQuote.DoubleQuote ? !att.Value.StartsWith("@") ? att.Value.Replace("\"", "&quot;") :
2463-
att.Value : quoteType == AttributeValueQuote.SingleQuote ? att.Value.Replace("'", "&#39;") : att.Value;
2468+
att.Value : quoteType == AttributeValueQuote.SingleQuote ? att.Value.Replace("'", "&#39;") : att.Value;
24642469
if (_ownerdocument.OptionOutputOptimizeAttributeValues)
24652470
if (att.Value.IndexOfAny(new char[] {(char) 10, (char) 13, (char) 9, ' '}) < 0)
24662471
outText.Write(" " + name + "=" + att.Value);
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
using System.IO;
2+
using System.Linq;
3+
using System.Net;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Globalization;
8+
using System.Threading;
9+
using Xunit;
10+
using System.Xml.Linq;
11+
12+
namespace HtmlAgilityPack.Tests.NetStandard2_0
13+
{
14+
15+
public class AttributeValueQuoteTests
16+
{
17+
public static string GlobalHtml1 = "<div singlequote='value' doublequote=\"value\" none=value withoutvalue></div>";
18+
19+
[Fact]
20+
public void GlobalAttributeValueQuote_DoubleQuote()
21+
{
22+
var doc = new HtmlDocument();
23+
doc.GlobalAttributeValueQuote = AttributeValueQuote.DoubleQuote;
24+
doc.LoadHtml(GlobalHtml1);
25+
26+
Assert.Equal("<div singlequote=\"value\" doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
27+
}
28+
29+
[Fact]
30+
public void GlobalAttributeValueQuote_SingleQuote()
31+
{
32+
var doc = new HtmlDocument();
33+
doc.GlobalAttributeValueQuote = AttributeValueQuote.SingleQuote;
34+
doc.LoadHtml(GlobalHtml1);
35+
36+
Assert.Equal("<div singlequote='value' doublequote='value' none='value' withoutvalue=''></div>", doc.DocumentNode.OuterHtml);
37+
}
38+
39+
[Fact]
40+
public void GlobalAttributeValueQuote_WithoutValue()
41+
{
42+
var doc = new HtmlDocument();
43+
doc.GlobalAttributeValueQuote = AttributeValueQuote.WithoutValue;
44+
doc.LoadHtml(GlobalHtml1);
45+
46+
Assert.Equal("<div singlequote doublequote none withoutvalue></div>", doc.DocumentNode.OuterHtml);
47+
}
48+
49+
[Fact]
50+
public void GlobalAttributeValueQuote_Initial()
51+
{
52+
var doc = new HtmlDocument();
53+
doc.GlobalAttributeValueQuote = AttributeValueQuote.Initial;
54+
doc.LoadHtml(GlobalHtml1);
55+
56+
Assert.Equal("<div singlequote='value' doublequote=\"value\" none=value withoutvalue></div>", doc.DocumentNode.OuterHtml);
57+
}
58+
59+
[Fact]
60+
public void GlobalAttributeValueQuote_InitialExceptWithoutValue()
61+
{
62+
var doc = new HtmlDocument();
63+
doc.GlobalAttributeValueQuote = AttributeValueQuote.InitialExceptWithoutValue;
64+
doc.LoadHtml(GlobalHtml1);
65+
66+
Assert.Equal("<div singlequote='value' doublequote=\"value\" none=value withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
67+
}
68+
69+
[Fact]
70+
public void GlobalAttributeValueQuote_None()
71+
{
72+
var doc = new HtmlDocument();
73+
doc.GlobalAttributeValueQuote = AttributeValueQuote.None;
74+
doc.LoadHtml(GlobalHtml1);
75+
76+
Assert.Equal("<div singlequote=value doublequote=value none=value withoutvalue=></div>", doc.DocumentNode.OuterHtml);
77+
}
78+
79+
[Fact]
80+
public void GlobalAttributeValueQuote_DoubleQuote_OutputAsXml()
81+
{
82+
var doc = new HtmlDocument();
83+
doc.GlobalAttributeValueQuote = AttributeValueQuote.DoubleQuote;
84+
doc.OptionOutputAsXml = true;
85+
doc.LoadHtml(GlobalHtml1);
86+
87+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote=\"value\" doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
88+
}
89+
90+
[Fact]
91+
public void GlobalAttributeValueQuote_SingleQuote_OutputAsXml()
92+
{
93+
var doc = new HtmlDocument();
94+
doc.GlobalAttributeValueQuote = AttributeValueQuote.SingleQuote;
95+
doc.OptionOutputAsXml = true;
96+
doc.LoadHtml(GlobalHtml1);
97+
98+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote='value' doublequote='value' none='value' withoutvalue=''></div>", doc.DocumentNode.OuterHtml);
99+
}
100+
101+
[Fact]
102+
public void GlobalAttributeValueQuote_WithoutValue_OutputAsXml()
103+
{
104+
var doc = new HtmlDocument();
105+
doc.GlobalAttributeValueQuote = AttributeValueQuote.WithoutValue;
106+
doc.OptionOutputAsXml = true;
107+
doc.LoadHtml(GlobalHtml1);
108+
109+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote=\"value\" doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
110+
}
111+
112+
[Fact]
113+
public void GlobalAttributeValueQuote_Initial_OutputAsXml()
114+
{
115+
var doc = new HtmlDocument();
116+
doc.GlobalAttributeValueQuote = AttributeValueQuote.Initial;
117+
doc.OptionOutputAsXml = true;
118+
doc.LoadHtml(GlobalHtml1);
119+
120+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote='value' doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
121+
}
122+
123+
[Fact]
124+
public void GlobalAttributeValueQuote_InitialExceptWithoutValue_OutputAsXml()
125+
{
126+
var doc = new HtmlDocument();
127+
doc.GlobalAttributeValueQuote = AttributeValueQuote.InitialExceptWithoutValue;
128+
doc.OptionOutputAsXml = true;
129+
doc.LoadHtml(GlobalHtml1);
130+
131+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote='value' doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
132+
}
133+
134+
[Fact]
135+
public void GlobalAttributeValueQuote_None_OutputAsXml()
136+
{
137+
var doc = new HtmlDocument();
138+
doc.GlobalAttributeValueQuote = AttributeValueQuote.None;
139+
doc.OptionOutputAsXml = true;
140+
doc.LoadHtml(GlobalHtml1);
141+
142+
Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?><div singlequote=\"value\" doublequote=\"value\" none=\"value\" withoutvalue=\"\"></div>", doc.DocumentNode.OuterHtml);
143+
}
144+
}
145+
}

src/Tests/HtmlAgilityPack.Tests.NetStandard2_0/HtmlDocument.PreserveOriginalTest.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,30 @@ public void PreserveClonedEmptyAttributesTest()
161161
Assert.Equal(@"<list-counter formErrorsCounter></list-counter>", cloned.OuterHtml);
162162
}
163163

164+
[Fact]
165+
public void PreserveEmptyAttributesWithInitialTest()
166+
{
167+
var d = new HtmlDocument { GlobalAttributeValueQuote = AttributeValueQuote.InitialExceptWithoutValue };
168+
d.LoadHtml("<bar ng-app class='message'></bar>");
169+
170+
var node = d.DocumentNode.SelectSingleNode("//bar");
171+
var outer = node.OuterHtml;
172+
173+
Assert.Equal("<bar ng-app=\"\" class='message'></bar>", outer);
174+
}
175+
176+
[Fact]
177+
public void PreserveEmptyAttributes()
178+
{
179+
var d = new HtmlDocument { GlobalAttributeValueQuote = AttributeValueQuote.InitialExceptWithoutValue };
180+
d.LoadHtml("<li ng>Nothing to show</li>");
181+
182+
var node = d.DocumentNode.SelectSingleNode("//li");
183+
var outer = node.OuterHtml;
184+
185+
Assert.Equal($"<li ng=\"\">Nothing to show</li>", outer);
186+
}
187+
164188
[Fact]
165189
public void PreserveQuoteTypeForLoadedAttributes()
166190
{
@@ -173,5 +197,16 @@ public void PreserveQuoteTypeForLoadedAttributes()
173197
// Result is: QuoteType: WithoutValue
174198
Assert.Equal(AttributeValueQuote.WithoutValue, checkedAttribute.QuoteType);
175199
}
200+
[Fact]
201+
public void PreserveQuoteTypeForLoadedAttributes2()
202+
{
203+
var d = new HtmlDocument { GlobalAttributeValueQuote = AttributeValueQuote.InitialExceptWithoutValue };
204+
d.LoadHtml(@"<bar ng-app ng-app2='message'></bar>");
205+
206+
var node = d.DocumentNode.SelectSingleNode("//bar");
207+
var outer = node.OuterHtml;
208+
209+
Assert.Equal($"<bar ng-app=\"\" ng-app2='message'></bar>", outer);
210+
}
176211
}
177212
}

0 commit comments

Comments
 (0)