XmlWriter.Create は、XmlWellFormedWriter にラップされた特定の XmlRawWriter を返します。これらはすべて内部として定義されているため、拡張することはできません。
ただし、XmlTextWriter を拡張することはできますが、整形式のライターに比べて機能セットが非常に限られています。
そう...
この問題に対処するために作成したカスタム XmlTextWriter を次に示します。これには、XmlWriterSettings を除いて、整形式のライターに欠けている機能のほとんどが含まれています。
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace System.Xml
{
public class CustomXmlTextWriter : XmlTextWriter
{
internal class CustomStreamWriter : StreamWriter
{
public CustomStreamWriter(Stream stream, Encoding encoding) : base(stream) { }
// This prevents the XmlTextWriter from writing the extra space before attributes, and the short EndElement " />"
public bool DisableSpace { get; set; }
public override void Write(char value)
{
if (DisableSpace && value == ' ') return;
else base.Write(value);
}
public override void Write(string value)
{
if (DisableSpace && value == " /") base.Write('/');
else base.Write(value);
}
}
public CustomXmlTextWriter(string filename, XmlWriterSettings settings) : this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read), settings) { }
public CustomXmlTextWriter(Stream stream, XmlWriterSettings settings) : this(new CustomStreamWriter(stream, settings.Encoding), settings) { }
internal CustomXmlTextWriter(CustomStreamWriter writer, XmlWriterSettings settings)
: base(writer)
{
m_Writer = writer;
m_Settings = settings;
if (m_Settings.OmitXmlDeclaration == false)
{
string encoding = (m_Writer.Encoding.CodePage == 1201) ? "UTF-16BE" : m_Writer.Encoding.WebName;
m_Writer.WriteLine("<?xml version=\"1.0\" encoding=\"{0}\"?>", encoding);
}
}
private bool m_HasAttributes = false;
private Stack<bool> m_HasAttributesStack = new Stack<bool>();
private CustomStreamWriter m_Writer;
private XmlWriterSettings m_Settings;
public override XmlWriterSettings Settings { get { return m_Settings; } }
public override void WriteStartElement(string prefix, string localName, string ns)
{
if (WriteState == WriteState.Element)
{
if (m_HasAttributes && Settings.NewLineOnAttributes) { WriteIndent(m_HasAttributesStack.Count); }
WriteRaw(""); // Trick the XmlTextWriter into closing the previous element, and updating the WriteState
m_Writer.DisableSpace = false;
}
int indentLevel = m_HasAttributesStack.Count;
if (indentLevel > 0)
{
WriteIndent(indentLevel);
}
m_HasAttributesStack.Push(m_HasAttributes);
m_HasAttributes = false;
base.WriteStartElement(prefix, localName, ns);
}
public override void WriteEndElement()
{
if (m_HasAttributes && Settings.NewLineOnAttributes)
{
WriteIndent(m_HasAttributesStack.Count - 1);
}
m_HasAttributes = m_HasAttributesStack.Pop();
base.WriteEndElement();
m_Writer.DisableSpace = false;
}
public override void WriteFullEndElement()
{
m_HasAttributes = m_HasAttributesStack.Pop();
WriteIndent(m_HasAttributesStack.Count);
base.WriteFullEndElement();
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
if (Settings.NewLineOnAttributes)
{
WriteIndent(m_HasAttributesStack.Count);
m_Writer.DisableSpace = true;
}
m_HasAttributes = true;
base.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteString(string text)
{
if (m_Settings.NewLineHandling == NewLineHandling.Replace)
{
text = Regex.Replace(text, @"\r\n?|\n", m_Settings.NewLineChars);
}
else if (m_Settings.NewLineHandling == NewLineHandling.Entitize)
{
text = Regex.Replace(text, @"\n|\r", m => String.Format("&#x{0:X};", (int)m.Value[0]));
}
base.WriteString(text);
}
private void WriteIndent(int indentLevel)
{
if (Settings.Indent == false) return;
m_Writer.Write(Settings.NewLineChars);
for (int i = 0; i < indentLevel; ++i)
{
m_Writer.Write(Settings.IndentChars);
}
}
}
}
プロジェクトで上記のコードを含むファイルを作成し、次のように使用します。
// Create the XmlWriter Settings as you normally would
// *Note: You can change or omit these, they are just for an example of what I supported
XmlWriterSettings settings = new XmlWriterSettings()
{
Encoding = Encoding.UTF8,
//OmitXmlDeclaration = true,
Indent = true,
//IndentChars = " ",
IndentChars = "\t",
NewLineOnAttributes = true,
//NewLineHandling = NewLineHandling.Entitize,
//NewLineHandling = NewLineHandling.Replace,
//NewLineChars = @"\n",
};
// Replace XmlWriter.Create with new CustomXmlTextWriter
//using (XmlWriter writer = XmlWriter.Create(path, settings))
using (XmlWriter writer = new CustomXmlTextWriter(path, settings))
{
xml.WriteTo(writer);
}
この機能が、XmlWriterSettings のオプションとして適切な形式のライターに追加されただけでよいでしょう。
差分ツールの変更がオプションではない場合があります (またはそもそも問題でさえありません) ツールを介して perforce のようなシステムを使用して変更を自動マージする場合、変更をできるだけアトミックに保つことが最善です。
たとえば...最後の属性が1人によって変更され、追加の属性が別の人によって最後に追加された場合、どの行に終了> (または/>)を含める必要があるかに基づいて人為的な競合を作成する理由はありません)。このシナリオの最善の解決策は、閉じ括弧が独自の行にあり、競合をすべて回避することです。