Skip to content

Commit 5f55f89

Browse files
committed
Fix issue microsoft#430: Autoformat on Save with empty XML-Elements.
Formatting Options has new setting to format Xml attributes each on a separate line. Publish new version 2.9.0.16
1 parent 74b7235 commit 5f55f89

File tree

14 files changed

+223
-21
lines changed

14 files changed

+223
-21
lines changed

docs/assets/images/options.jpg

-6.24 KB
Loading

docs/help/options.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ will automatically reload that file (unless you have pending unsaved edits).
2121

2222
### Formatting
2323
You can also configure the formatting options used when you save an XML file, or turn off formatting
24-
altogether. Preserve Whitepsace also controls how the file is opened; when true, you will see all the whitespace nodes in
24+
altogether. Preserve Whitespace controls how the file is opened; when true, you will see all the whitespace nodes in
2525
the document, which are used also when the file is saved. You can also configure the TreeView indentation level in
26-
pixels.
26+
pixels. You can also format attributes so they each have a separate line.
2727

2828
### Language
2929
Specify which language annotations to pick from associated XSD schemas.

src/Application/FormOptions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ public class UserSettings
238238
private bool _schemaAwareText;
239239
private string _schemaAwareNames;
240240
private bool _promptOnReload;
241+
private bool _attributesOnNewLine;
241242

242243
public UserSettings(Settings s)
243244
{
@@ -256,6 +257,7 @@ public UserSettings(Settings s)
256257
_noByteOrderMark = this._settings.GetBoolean("NoByteOrderMark");
257258
_indentLevel = this._settings.GetInteger("IndentLevel");
258259
_indentChar = (IndentChar)this._settings["IndentChar"];
260+
_attributesOnNewLine = this._settings.GetBoolean("AttributesOnNewLine");
259261
_newLineChars = this._settings.GetString("NewLineChars");
260262
_preserveWhitespace = this._settings.GetBoolean("PreserveWhitespace");
261263
_language = this._settings.GetString("Language");
@@ -359,6 +361,7 @@ public void Apply()
359361
this._settings["NewLineChars"] = _newLineChars;
360362
this._settings["PreserveWhitespace"] = _preserveWhitespace;
361363
this._settings["NoByteOrderMark"] = _noByteOrderMark;
364+
this._settings["AttributesOnNewLine"] = _attributesOnNewLine;
362365
this._settings.SetLocation(_settingsLocation);
363366

364367
this._settings["Language"] = ("" + this._language).Trim();
@@ -406,6 +409,7 @@ public void Reset()
406409
_disableDefaultXslt = false;
407410
_noByteOrderMark = false;
408411
_indentLevel = 2;
412+
_attributesOnNewLine = false;
409413
_indentChar = IndentChar.Space;
410414
_newLineChars = Settings.EscapeNewLines("\r\n");
411415
_language = "";
@@ -840,6 +844,21 @@ public bool NoByteOrderMark
840844
}
841845
}
842846

847+
[SRCategory("FormatCategory")]
848+
[LocDisplayName("AttributesOnNewLineProperty")]
849+
[SRDescription("AttributesOnNewLineDescription")]
850+
public bool AttributesOnNewLine
851+
{
852+
get
853+
{
854+
return this._attributesOnNewLine;
855+
}
856+
set
857+
{
858+
this._attributesOnNewLine = value;
859+
}
860+
}
861+
843862
[SRCategory("LongLineCategory")]
844863
[LocDisplayName("MaximumLineLengthProperty")]
845864
[SRDescription("MaximumLineLengthDescription")]

src/Model/Settings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ public void SetDefaults()
974974
this["PreserveWhitespace"] = false;
975975
this["Language"] = "";
976976
this["NoByteOrderMark"] = false;
977+
this["AttributesOnNewLine"] = false;
977978

978979
this["AppRegistered"] = false;
979980
this["MaximumLineLength"] = 10000;

src/Model/Utilities.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ public static void InitializeWriterSettings(XmlWriterSettings settings, IService
227227
char ch = (indentChar == IndentChar.Space) ? ' ' : '\t';
228228
settings.IndentChars = new string(ch, indentLevel);
229229
settings.NewLineChars = Settings.UnescapeNewLines(s.GetString("NewLineChars", "\r\n"));
230+
settings.NewLineOnAttributes = s.GetBoolean("AttributesOnNewLine");
230231
}
231232
}
232233
}

src/Model/XmlCache.cs

Lines changed: 164 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Text;
77
using System.Xml;
8+
using System.Xml.Linq;
89
using System.Xml.Schema;
910
using System.Xml.XPath;
1011

@@ -387,9 +388,13 @@ public void SaveCopy(string filename)
387388

388389
var encoding = GetEncoding();
389390
s.Encoding = encoding;
391+
390392
bool noBom = false;
393+
bool useNewWriter = true;
391394
if (this._site != null)
392395
{
396+
EncodingHelpers.InitializeWriterSettings(s, this._site);
397+
393398
Settings settings = (Settings)this._site.GetService(typeof(Settings));
394399
if (settings != null)
395400
{
@@ -401,37 +406,183 @@ public void SaveCopy(string filename)
401406
}
402407
}
403408
}
404-
if (noBom)
409+
410+
MemoryStream ms = new MemoryStream();
411+
if (useNewWriter)
412+
{
413+
using (var writer = XmlWriter.Create(ms, s))
414+
{
415+
this.WriteTo(writer);
416+
}
417+
}
418+
else
405419
{
406-
MemoryStream ms = new MemoryStream();
407-
// The new XmlWriter.Create method returns a writer that is too strict and does not
408-
// allow xmlns attributes that override the parent element NamespaceURI. Using that
409-
// writer would require very complex (and very slow) recreation of XML Element nodes
410-
// in the tree (and therefore all their children also) every time and xmlns attribute
411-
// is modified.
412420
using (XmlTextWriter w = new XmlTextWriter(ms, encoding))
413421
{
414422
EncodingHelpers.InitializeWriterSettings(w, this._site);
415-
_doc.Save(w);
423+
this.WriteTo(w);
416424
}
425+
}
417426

427+
if (noBom)
428+
{
418429
using (var stm = new MemoryStream(ms.ToArray()))
419430
{
420431
EncodingHelpers.WriteFileWithoutBOM(stm, filename);
421432
}
422433
}
423434
else
424435
{
425-
using (XmlTextWriter w = new XmlTextWriter(filename, encoding))
436+
// doing the write this way ensures that an XML exception doesn't result in
437+
// wiping the previous state of the file on disk.
438+
File.WriteAllBytes(filename, ms.ToArray());
439+
}
440+
}
441+
finally
442+
{
443+
StartFileWatch();
444+
}
445+
}
446+
447+
private void WriteTo(XmlWriter w)
448+
{
449+
// The new XmlWriter.Create method returns a writer that is strict and does not
450+
// allow xmlns attributes that override the parent element NamespaceURI. Fixing that
451+
// in the XmlElement tree would require very complex (and very slow) recreation of
452+
// XML Element nodes in the tree (and therefore all their children also) every time an
453+
// xmlns attribute is modified. So we deal with that here instead during save by calling
454+
// the XmlWriter ourselves with the correct namespaces on the WriteStartElement call.
455+
XmlNode xmlNode = _doc.FirstChild;
456+
if (xmlNode == null)
457+
{
458+
return;
459+
}
460+
if (w.WriteState == WriteState.Start)
461+
{
462+
if (xmlNode is XmlDeclaration)
463+
{
464+
if (Standalone.Length == 0)
426465
{
427-
EncodingHelpers.InitializeWriterSettings(w, this._site);
428-
_doc.Save(w);
466+
w.WriteStartDocument();
467+
}
468+
else if (Standalone == "yes")
469+
{
470+
w.WriteStartDocument(standalone: true);
429471
}
472+
else if (Standalone == "no")
473+
{
474+
w.WriteStartDocument(standalone: false);
475+
}
476+
xmlNode = xmlNode.NextSibling;
477+
}
478+
else
479+
{
480+
w.WriteStartDocument();
430481
}
431482
}
432-
finally
483+
var scope = new XmlNamespaceManager(_doc.NameTable);
484+
while (xmlNode != null)
433485
{
434-
StartFileWatch();
486+
WriteNode(xmlNode, w, scope);
487+
xmlNode = xmlNode.NextSibling;
488+
}
489+
w.Flush();
490+
}
491+
492+
internal void WriteNode(XmlNode node, XmlWriter w, XmlNamespaceManager scope)
493+
{
494+
if (node is XmlElement e)
495+
{
496+
WriteElementTo(w, e, scope);
497+
}
498+
else
499+
{
500+
node.WriteTo(w);
501+
}
502+
}
503+
504+
private void WriteElementTo(XmlWriter writer, XmlElement e, XmlNamespaceManager scope)
505+
{
506+
XmlNode xmlNode = e;
507+
XmlNode xmlNode2 = e;
508+
while (true)
509+
{
510+
e = xmlNode2 as XmlElement;
511+
if (e != null)
512+
{
513+
scope.PushScope();
514+
for (int i = 0; i < e.Attributes.Count; i++)
515+
{
516+
XmlAttribute xmlAttribute = e.Attributes[i];
517+
if (xmlAttribute.NamespaceURI == XmlStandardUris.XmlnsUri)
518+
{
519+
var prefix = xmlAttribute.Prefix == "xmlns" ? xmlAttribute.LocalName : "";
520+
scope.AddNamespace(prefix, xmlAttribute.Value);
521+
}
522+
}
523+
524+
WriteStartElement(writer, e, scope);
525+
if (e.IsEmpty)
526+
{
527+
writer.WriteEndElement();
528+
scope.PopScope();
529+
}
530+
else
531+
{
532+
if (e.LastChild != null)
533+
{
534+
xmlNode2 = e.FirstChild;
535+
continue;
536+
}
537+
writer.WriteFullEndElement();
538+
scope.PopScope();
539+
}
540+
}
541+
else
542+
{
543+
WriteNode(xmlNode2, writer, scope);
544+
}
545+
while (xmlNode2 != xmlNode && xmlNode2 == xmlNode2.ParentNode.LastChild)
546+
{
547+
xmlNode2 = xmlNode2.ParentNode;
548+
writer.WriteFullEndElement();
549+
scope.PopScope();
550+
}
551+
if (xmlNode2 != xmlNode)
552+
{
553+
xmlNode2 = xmlNode2.NextSibling;
554+
continue;
555+
}
556+
break;
557+
}
558+
}
559+
560+
private void WriteStartElement(XmlWriter w, XmlElement e, XmlNamespaceManager scope)
561+
{
562+
// Fix up the element namespace so that the XmlWriter doesn't complain!
563+
var ns = scope.LookupNamespace(e.Prefix);
564+
w.WriteStartElement(e.Prefix, e.LocalName, ns);
565+
if (e.HasAttributes)
566+
{
567+
XmlAttributeCollection xmlAttributeCollection = e.Attributes;
568+
for (int i = 0; i < xmlAttributeCollection.Count; i++)
569+
{
570+
XmlAttribute xmlAttribute = xmlAttributeCollection[i];
571+
xmlAttribute.WriteTo(w);
572+
}
573+
}
574+
}
575+
576+
577+
public string Standalone
578+
{
579+
get
580+
{
581+
if (this._doc.FirstChild is XmlDeclaration x)
582+
{
583+
return x.Standalone;
584+
}
585+
return "";
435586
}
436587
}
437588

src/Updates/Updates.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
<history>https://github.com/microsoft/XmlNotepad/blob/master/src/Updates/Updates.xml</history>
1010
<frequency>1.00:00:00</frequency>
1111
</application>
12+
<version number="2.9.0.16">
13+
<bug>Fix issue #430: Autoformat on Save with empty XML-Elements.</bug>
14+
<feature>Formatting Options has new setting to format Xml attributes each on a separate line.</feature>
15+
</version>
1216
<version number="2.9.0.15">
1317
<bug>Fix issue #425: "No byte order mark on save" option throws stream closed exception add unit test.</bug>
1418
<bug>Apply dark mode to window titlebar.</bug>

src/Version/Version.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
//
1818
// Version.props is the Master version number from which UpdateVersions will propagate to
1919
// this file, Package.appxmanifest, Product.wxs, Bundle.wxs and Application.csproj.
20-
[assembly: AssemblyVersion("2.9.0.15")]
21-
[assembly: AssemblyFileVersion("2.9.0.15")]
20+
[assembly: AssemblyVersion("2.9.0.16")]
21+
[assembly: AssemblyFileVersion("2.9.0.16")]

src/Version/Version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<PropertyGroup>
44
<ApplicationRevision>0</ApplicationRevision>
5-
<ApplicationVersion>2.9.0.15</ApplicationVersion>
5+
<ApplicationVersion>2.9.0.16</ApplicationVersion>
66
<Version>$(ApplicationVersion)</Version>
77
<Authors>Chris Lovett</Authors>
88
<Product>XmlNotepad</Product>

src/XmlNotepad/StringResources.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/XmlNotepad/StringResources.resx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,14 @@ Do you want to enable execution of this script code?</value>
756756
<data name="LongLineCategory" xml:space="preserve">
757757
<value>Long Lines</value>
758758
<comment>Property Grid Category</comment>
759+
</data>
760+
<data name="AttributesOnNewLineProperty" xml:space="preserve">
761+
<value>Attributes on a new line</value>
762+
<comment>Property Grid Category</comment>
763+
</data>
764+
<data name="AttributesOnNewLineDescription" xml:space="preserve">
765+
<value>Format each attribute on a new line with matching indentation.</value>
766+
<comment>Property Grid description</comment>
759767
</data>
760768
<data name="MaximumLineLengthDescription" xml:space="preserve">
761769
<value>What is the maximum line length before prompting for reformatting?</value>

src/XmlNotepadBundle/Bundle.wxs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
3-
<Bundle Name="XML Notepad" Version="2.9.0.15" Manufacturer="Lovett Software" UpgradeCode="b39cd92c-6bf2-4f4c-9e2c-fb402781a316">
3+
<Bundle Name="XML Notepad" Version="2.9.0.16" Manufacturer="Lovett Software" UpgradeCode="b39cd92c-6bf2-4f4c-9e2c-fb402781a316">
44
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
55
<bal:WixStandardBootstrapperApplication LicenseFile="..\Application\license.rtf" ShowVersion="yes" />
66
</BootstrapperApplicationRef>

src/XmlNotepadPackage/Package.appxmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap rescap">
3-
<Identity Name="43906ChrisLovett.XmlNotepad" Publisher="CN=BC801FCC-0BF8-49D7-9F51-1B625C3BE476" Version="2.9.0.15" />
3+
<Identity Name="43906ChrisLovett.XmlNotepad" Publisher="CN=BC801FCC-0BF8-49D7-9F51-1B625C3BE476" Version="2.9.0.16" />
44
<Properties>
55
<DisplayName>XmlNotepad</DisplayName>
66
<PublisherDisplayName>Chris Lovett</PublisherDisplayName>

src/XmlNotepadSetup/Product.wxs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
3-
<Product Id="*" Name="XmlNotepad" Language="1033" Version="2.9.0.15" Manufacturer="Lovett Software" UpgradeCode="14C1B5E8-3198-4AF2-B4BC-351017A109D3">
3+
<Product Id="*" Name="XmlNotepad" Language="1033" Version="2.9.0.16" Manufacturer="Lovett Software" UpgradeCode="14C1B5E8-3198-4AF2-B4BC-351017A109D3">
44
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
55
<MajorUpgrade Schedule="afterInstallFinalize" DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes" />
66
<MediaTemplate />

0 commit comments

Comments
 (0)