Skip to content

Commit

Permalink
Fix issue microsoft#430: Autoformat on Save with empty XML-Elements.
Browse files Browse the repository at this point in the history
Formatting Options has new setting to format Xml attributes each on a separate line.
Publish new version 2.9.0.16
  • Loading branch information
lovettchris committed Dec 10, 2024
1 parent 74b7235 commit 5f55f89
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 21 deletions.
Binary file modified docs/assets/images/options.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/help/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ will automatically reload that file (unless you have pending unsaved edits).

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

### Language
Specify which language annotations to pick from associated XSD schemas.
Expand Down
19 changes: 19 additions & 0 deletions src/Application/FormOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ public class UserSettings
private bool _schemaAwareText;
private string _schemaAwareNames;
private bool _promptOnReload;
private bool _attributesOnNewLine;

public UserSettings(Settings s)
{
Expand All @@ -256,6 +257,7 @@ public UserSettings(Settings s)
_noByteOrderMark = this._settings.GetBoolean("NoByteOrderMark");
_indentLevel = this._settings.GetInteger("IndentLevel");
_indentChar = (IndentChar)this._settings["IndentChar"];
_attributesOnNewLine = this._settings.GetBoolean("AttributesOnNewLine");
_newLineChars = this._settings.GetString("NewLineChars");
_preserveWhitespace = this._settings.GetBoolean("PreserveWhitespace");
_language = this._settings.GetString("Language");
Expand Down Expand Up @@ -359,6 +361,7 @@ public void Apply()
this._settings["NewLineChars"] = _newLineChars;
this._settings["PreserveWhitespace"] = _preserveWhitespace;
this._settings["NoByteOrderMark"] = _noByteOrderMark;
this._settings["AttributesOnNewLine"] = _attributesOnNewLine;
this._settings.SetLocation(_settingsLocation);

this._settings["Language"] = ("" + this._language).Trim();
Expand Down Expand Up @@ -406,6 +409,7 @@ public void Reset()
_disableDefaultXslt = false;
_noByteOrderMark = false;
_indentLevel = 2;
_attributesOnNewLine = false;
_indentChar = IndentChar.Space;
_newLineChars = Settings.EscapeNewLines("\r\n");
_language = "";
Expand Down Expand Up @@ -840,6 +844,21 @@ public bool NoByteOrderMark
}
}

[SRCategory("FormatCategory")]
[LocDisplayName("AttributesOnNewLineProperty")]
[SRDescription("AttributesOnNewLineDescription")]
public bool AttributesOnNewLine
{
get
{
return this._attributesOnNewLine;
}
set
{
this._attributesOnNewLine = value;
}
}

[SRCategory("LongLineCategory")]
[LocDisplayName("MaximumLineLengthProperty")]
[SRDescription("MaximumLineLengthDescription")]
Expand Down
1 change: 1 addition & 0 deletions src/Model/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,7 @@ public void SetDefaults()
this["PreserveWhitespace"] = false;
this["Language"] = "";
this["NoByteOrderMark"] = false;
this["AttributesOnNewLine"] = false;

this["AppRegistered"] = false;
this["MaximumLineLength"] = 10000;
Expand Down
1 change: 1 addition & 0 deletions src/Model/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public static void InitializeWriterSettings(XmlWriterSettings settings, IService
char ch = (indentChar == IndentChar.Space) ? ' ' : '\t';
settings.IndentChars = new string(ch, indentLevel);
settings.NewLineChars = Settings.UnescapeNewLines(s.GetString("NewLineChars", "\r\n"));
settings.NewLineOnAttributes = s.GetBoolean("AttributesOnNewLine");
}
}
}
Expand Down
177 changes: 164 additions & 13 deletions src/Model/XmlCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.XPath;

Expand Down Expand Up @@ -387,9 +388,13 @@ public void SaveCopy(string filename)

var encoding = GetEncoding();
s.Encoding = encoding;

bool noBom = false;
bool useNewWriter = true;
if (this._site != null)
{
EncodingHelpers.InitializeWriterSettings(s, this._site);

Settings settings = (Settings)this._site.GetService(typeof(Settings));
if (settings != null)
{
Expand All @@ -401,37 +406,183 @@ public void SaveCopy(string filename)
}
}
}
if (noBom)

MemoryStream ms = new MemoryStream();
if (useNewWriter)
{
using (var writer = XmlWriter.Create(ms, s))
{
this.WriteTo(writer);
}
}
else
{
MemoryStream ms = new MemoryStream();
// The new XmlWriter.Create method returns a writer that is too strict and does not
// allow xmlns attributes that override the parent element NamespaceURI. Using that
// writer would require very complex (and very slow) recreation of XML Element nodes
// in the tree (and therefore all their children also) every time and xmlns attribute
// is modified.
using (XmlTextWriter w = new XmlTextWriter(ms, encoding))
{
EncodingHelpers.InitializeWriterSettings(w, this._site);
_doc.Save(w);
this.WriteTo(w);
}
}

if (noBom)
{
using (var stm = new MemoryStream(ms.ToArray()))
{
EncodingHelpers.WriteFileWithoutBOM(stm, filename);
}
}
else
{
using (XmlTextWriter w = new XmlTextWriter(filename, encoding))
// doing the write this way ensures that an XML exception doesn't result in
// wiping the previous state of the file on disk.
File.WriteAllBytes(filename, ms.ToArray());
}
}
finally
{
StartFileWatch();
}
}

private void WriteTo(XmlWriter w)
{
// The new XmlWriter.Create method returns a writer that is strict and does not
// allow xmlns attributes that override the parent element NamespaceURI. Fixing that
// in the XmlElement tree would require very complex (and very slow) recreation of
// XML Element nodes in the tree (and therefore all their children also) every time an
// xmlns attribute is modified. So we deal with that here instead during save by calling
// the XmlWriter ourselves with the correct namespaces on the WriteStartElement call.
XmlNode xmlNode = _doc.FirstChild;
if (xmlNode == null)
{
return;
}
if (w.WriteState == WriteState.Start)
{
if (xmlNode is XmlDeclaration)
{
if (Standalone.Length == 0)
{
EncodingHelpers.InitializeWriterSettings(w, this._site);
_doc.Save(w);
w.WriteStartDocument();
}
else if (Standalone == "yes")
{
w.WriteStartDocument(standalone: true);
}
else if (Standalone == "no")
{
w.WriteStartDocument(standalone: false);
}
xmlNode = xmlNode.NextSibling;
}
else
{
w.WriteStartDocument();
}
}
finally
var scope = new XmlNamespaceManager(_doc.NameTable);
while (xmlNode != null)
{
StartFileWatch();
WriteNode(xmlNode, w, scope);
xmlNode = xmlNode.NextSibling;
}
w.Flush();
}

internal void WriteNode(XmlNode node, XmlWriter w, XmlNamespaceManager scope)
{
if (node is XmlElement e)
{
WriteElementTo(w, e, scope);
}
else
{
node.WriteTo(w);
}
}

private void WriteElementTo(XmlWriter writer, XmlElement e, XmlNamespaceManager scope)
{
XmlNode xmlNode = e;
XmlNode xmlNode2 = e;
while (true)
{
e = xmlNode2 as XmlElement;
if (e != null)
{
scope.PushScope();
for (int i = 0; i < e.Attributes.Count; i++)
{
XmlAttribute xmlAttribute = e.Attributes[i];
if (xmlAttribute.NamespaceURI == XmlStandardUris.XmlnsUri)
{
var prefix = xmlAttribute.Prefix == "xmlns" ? xmlAttribute.LocalName : "";
scope.AddNamespace(prefix, xmlAttribute.Value);
}
}

WriteStartElement(writer, e, scope);
if (e.IsEmpty)
{
writer.WriteEndElement();
scope.PopScope();
}
else
{
if (e.LastChild != null)
{
xmlNode2 = e.FirstChild;
continue;
}
writer.WriteFullEndElement();
scope.PopScope();
}
}
else
{
WriteNode(xmlNode2, writer, scope);
}
while (xmlNode2 != xmlNode && xmlNode2 == xmlNode2.ParentNode.LastChild)
{
xmlNode2 = xmlNode2.ParentNode;
writer.WriteFullEndElement();
scope.PopScope();
}
if (xmlNode2 != xmlNode)
{
xmlNode2 = xmlNode2.NextSibling;
continue;
}
break;
}
}

private void WriteStartElement(XmlWriter w, XmlElement e, XmlNamespaceManager scope)
{
// Fix up the element namespace so that the XmlWriter doesn't complain!
var ns = scope.LookupNamespace(e.Prefix);
w.WriteStartElement(e.Prefix, e.LocalName, ns);
if (e.HasAttributes)
{
XmlAttributeCollection xmlAttributeCollection = e.Attributes;
for (int i = 0; i < xmlAttributeCollection.Count; i++)
{
XmlAttribute xmlAttribute = xmlAttributeCollection[i];
xmlAttribute.WriteTo(w);
}
}
}


public string Standalone
{
get
{
if (this._doc.FirstChild is XmlDeclaration x)
{
return x.Standalone;
}
return "";
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Updates/Updates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<history>https://github.com/microsoft/XmlNotepad/blob/master/src/Updates/Updates.xml</history>
<frequency>1.00:00:00</frequency>
</application>
<version number="2.9.0.16">
<bug>Fix issue #430: Autoformat on Save with empty XML-Elements.</bug>
<feature>Formatting Options has new setting to format Xml attributes each on a separate line.</feature>
</version>
<version number="2.9.0.15">
<bug>Fix issue #425: "No byte order mark on save" option throws stream closed exception add unit test.</bug>
<bug>Apply dark mode to window titlebar.</bug>
Expand Down
4 changes: 2 additions & 2 deletions src/Version/Version.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
//
// Version.props is the Master version number from which UpdateVersions will propagate to
// this file, Package.appxmanifest, Product.wxs, Bundle.wxs and Application.csproj.
[assembly: AssemblyVersion("2.9.0.15")]
[assembly: AssemblyFileVersion("2.9.0.15")]
[assembly: AssemblyVersion("2.9.0.16")]
[assembly: AssemblyFileVersion("2.9.0.16")]
2 changes: 1 addition & 1 deletion src/Version/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>2.9.0.15</ApplicationVersion>
<ApplicationVersion>2.9.0.16</ApplicationVersion>
<Version>$(ApplicationVersion)</Version>
<Authors>Chris Lovett</Authors>
<Product>XmlNotepad</Product>
Expand Down
18 changes: 18 additions & 0 deletions src/XmlNotepad/StringResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/XmlNotepad/StringResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,14 @@ Do you want to enable execution of this script code?</value>
<data name="LongLineCategory" xml:space="preserve">
<value>Long Lines</value>
<comment>Property Grid Category</comment>
</data>
<data name="AttributesOnNewLineProperty" xml:space="preserve">
<value>Attributes on a new line</value>
<comment>Property Grid Category</comment>
</data>
<data name="AttributesOnNewLineDescription" xml:space="preserve">
<value>Format each attribute on a new line with matching indentation.</value>
<comment>Property Grid description</comment>
</data>
<data name="MaximumLineLengthDescription" xml:space="preserve">
<value>What is the maximum line length before prompting for reformatting?</value>
Expand Down
2 changes: 1 addition & 1 deletion src/XmlNotepadBundle/Bundle.wxs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="XML Notepad" Version="2.9.0.15" Manufacturer="Lovett Software" UpgradeCode="b39cd92c-6bf2-4f4c-9e2c-fb402781a316">
<Bundle Name="XML Notepad" Version="2.9.0.16" Manufacturer="Lovett Software" UpgradeCode="b39cd92c-6bf2-4f4c-9e2c-fb402781a316">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">
<bal:WixStandardBootstrapperApplication LicenseFile="..\Application\license.rtf" ShowVersion="yes" />
</BootstrapperApplicationRef>
Expand Down
2 changes: 1 addition & 1 deletion src/XmlNotepadPackage/Package.appxmanifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<Identity Name="43906ChrisLovett.XmlNotepad" Publisher="CN=BC801FCC-0BF8-49D7-9F51-1B625C3BE476" Version="2.9.0.15" />
<Identity Name="43906ChrisLovett.XmlNotepad" Publisher="CN=BC801FCC-0BF8-49D7-9F51-1B625C3BE476" Version="2.9.0.16" />
<Properties>
<DisplayName>XmlNotepad</DisplayName>
<PublisherDisplayName>Chris Lovett</PublisherDisplayName>
Expand Down
2 changes: 1 addition & 1 deletion src/XmlNotepadSetup/Product.wxs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<Product Id="*" Name="XmlNotepad" Language="1033" Version="2.9.0.15" Manufacturer="Lovett Software" UpgradeCode="14C1B5E8-3198-4AF2-B4BC-351017A109D3">
<Product Id="*" Name="XmlNotepad" Language="1033" Version="2.9.0.16" Manufacturer="Lovett Software" UpgradeCode="14C1B5E8-3198-4AF2-B4BC-351017A109D3">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade Schedule="afterInstallFinalize" DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes" />
<MediaTemplate />
Expand Down

0 comments on commit 5f55f89

Please sign in to comment.