e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1017 lines
45 KiB
C#
1017 lines
45 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
namespace System.Xml
|
|
{
|
|
using System.Collections;
|
|
using System.IO;
|
|
using System.Runtime;
|
|
using System.Runtime.Serialization;
|
|
using System.Text;
|
|
|
|
sealed class XmlCanonicalWriter
|
|
{
|
|
XmlUTF8NodeWriter writer;
|
|
MemoryStream elementStream;
|
|
byte[] elementBuffer;
|
|
XmlUTF8NodeWriter elementWriter;
|
|
bool inStartElement;
|
|
int depth;
|
|
Scope[] scopes;
|
|
int xmlnsAttributeCount;
|
|
XmlnsAttribute[] xmlnsAttributes;
|
|
int attributeCount;
|
|
Attribute[] attributes;
|
|
Attribute attribute;
|
|
Element element;
|
|
byte[] xmlnsBuffer;
|
|
int xmlnsOffset;
|
|
const int maxBytesPerChar = 3;
|
|
int xmlnsStartOffset;
|
|
bool includeComments;
|
|
string[] inclusivePrefixes;
|
|
const string xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
|
|
|
|
static readonly bool[] isEscapedAttributeChar = new bool[]
|
|
{
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, // All
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
false, false, true, false, false, false, true, false, false, false, false, false, false, false, false, false, // '"', '&'
|
|
false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false // '<'
|
|
};
|
|
static readonly bool[] isEscapedElementChar = new bool[]
|
|
{
|
|
true, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, // All but 0x09, 0x0A
|
|
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
|
|
false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, // '&'
|
|
false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, false // '<', '>'
|
|
};
|
|
|
|
public XmlCanonicalWriter()
|
|
{
|
|
}
|
|
|
|
public void SetOutput(Stream stream, bool includeComments, string[] inclusivePrefixes)
|
|
{
|
|
if (stream == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("stream");
|
|
|
|
if (writer == null)
|
|
{
|
|
writer = new XmlUTF8NodeWriter(isEscapedAttributeChar, isEscapedElementChar);
|
|
}
|
|
writer.SetOutput(stream, false, null);
|
|
|
|
if (elementStream == null)
|
|
{
|
|
elementStream = new MemoryStream();
|
|
}
|
|
|
|
if (elementWriter == null)
|
|
{
|
|
elementWriter = new XmlUTF8NodeWriter(isEscapedAttributeChar, isEscapedElementChar);
|
|
}
|
|
elementWriter.SetOutput(elementStream, false, null);
|
|
|
|
if (xmlnsAttributes == null)
|
|
{
|
|
xmlnsAttributeCount = 0;
|
|
xmlnsOffset = 0;
|
|
WriteXmlnsAttribute("xml", "http://www.w3.org/XML/1998/namespace");
|
|
WriteXmlnsAttribute("xmlns", xmlnsNamespace);
|
|
WriteXmlnsAttribute(string.Empty, string.Empty);
|
|
xmlnsStartOffset = xmlnsOffset;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
xmlnsAttributes[i].referred = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xmlnsAttributeCount = 3;
|
|
xmlnsOffset = xmlnsStartOffset;
|
|
}
|
|
|
|
depth = 0;
|
|
inStartElement = false;
|
|
this.includeComments = includeComments;
|
|
this.inclusivePrefixes = null;
|
|
if (inclusivePrefixes != null)
|
|
{
|
|
this.inclusivePrefixes = new string[inclusivePrefixes.Length];
|
|
for (int i = 0; i < inclusivePrefixes.Length; ++i)
|
|
{
|
|
if (inclusivePrefixes[i] == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.InvalidInclusivePrefixListCollection));
|
|
}
|
|
this.inclusivePrefixes[i] = inclusivePrefixes[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Flush()
|
|
{
|
|
ThrowIfClosed();
|
|
writer.Flush();
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
if (writer != null)
|
|
writer.Close();
|
|
if (elementWriter != null)
|
|
elementWriter.Close();
|
|
if (elementStream != null && elementStream.Length > 512)
|
|
elementStream = null;
|
|
elementBuffer = null;
|
|
if (scopes != null && scopes.Length > 16)
|
|
scopes = null;
|
|
if (attributes != null && attributes.Length > 16)
|
|
attributes = null;
|
|
if (xmlnsBuffer != null && xmlnsBuffer.Length > 1024)
|
|
{
|
|
xmlnsAttributes = null;
|
|
xmlnsBuffer = null;
|
|
}
|
|
inclusivePrefixes = null;
|
|
}
|
|
|
|
public void WriteDeclaration()
|
|
{
|
|
}
|
|
|
|
public void WriteComment(string value)
|
|
{
|
|
if (value == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
ThrowIfClosed();
|
|
if (includeComments)
|
|
{
|
|
writer.WriteComment(value);
|
|
}
|
|
}
|
|
|
|
|
|
void StartElement()
|
|
{
|
|
if (scopes == null)
|
|
{
|
|
scopes = new Scope[4];
|
|
}
|
|
else if (depth == scopes.Length)
|
|
{
|
|
Scope[] newScopes = new Scope[depth * 2];
|
|
Array.Copy(scopes, newScopes, depth);
|
|
scopes = newScopes;
|
|
}
|
|
scopes[depth].xmlnsAttributeCount = xmlnsAttributeCount;
|
|
scopes[depth].xmlnsOffset = xmlnsOffset;
|
|
depth++;
|
|
inStartElement = true;
|
|
attributeCount = 0;
|
|
elementStream.Position = 0;
|
|
}
|
|
|
|
void EndElement()
|
|
{
|
|
depth--;
|
|
xmlnsAttributeCount = scopes[depth].xmlnsAttributeCount;
|
|
xmlnsOffset = scopes[depth].xmlnsOffset;
|
|
}
|
|
|
|
public void WriteStartElement(string prefix, string localName)
|
|
{
|
|
if (prefix == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("prefix");
|
|
if (localName == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
|
|
ThrowIfClosed();
|
|
bool isRootElement = (this.depth == 0);
|
|
|
|
StartElement();
|
|
element.prefixOffset = elementWriter.Position + 1;
|
|
element.prefixLength = Encoding.UTF8.GetByteCount(prefix);
|
|
element.localNameOffset = element.prefixOffset + element.prefixLength + (element.prefixLength != 0 ? 1 : 0);
|
|
element.localNameLength = Encoding.UTF8.GetByteCount(localName);
|
|
elementWriter.WriteStartElement(prefix, localName);
|
|
|
|
// If we have a inclusivenamespace prefix list and the namespace declaration is in the
|
|
// outer context, then Add it to the root element.
|
|
if (isRootElement && (this.inclusivePrefixes != null))
|
|
{
|
|
// Scan through all the namespace declarations in the outer scope.
|
|
for (int i = 0; i < this.scopes[0].xmlnsAttributeCount; ++i)
|
|
{
|
|
if (IsInclusivePrefix(ref xmlnsAttributes[i]))
|
|
{
|
|
XmlnsAttribute attribute = xmlnsAttributes[i];
|
|
AddXmlnsAttribute(ref attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteStartElement(byte[] prefixBuffer, int prefixOffset, int prefixLength, byte[] localNameBuffer, int localNameOffset, int localNameLength)
|
|
{
|
|
if (prefixBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("prefixBuffer"));
|
|
if (prefixOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixOffset > prefixBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.OffsetExceedsBufferSize, prefixBuffer.Length)));
|
|
if (prefixLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixLength > prefixBuffer.Length - prefixOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, prefixBuffer.Length - prefixOffset)));
|
|
|
|
if (localNameBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("localNameBuffer"));
|
|
if (localNameOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (localNameOffset > localNameBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameOffset", SR.GetString(SR.OffsetExceedsBufferSize, localNameBuffer.Length)));
|
|
if (localNameLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (localNameLength > localNameBuffer.Length - localNameOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, localNameBuffer.Length - localNameOffset)));
|
|
ThrowIfClosed();
|
|
bool isRootElement = (this.depth == 0);
|
|
|
|
StartElement();
|
|
element.prefixOffset = elementWriter.Position + 1;
|
|
element.prefixLength = prefixLength;
|
|
element.localNameOffset = element.prefixOffset + prefixLength + (prefixLength != 0 ? 1 : 0);
|
|
element.localNameLength = localNameLength;
|
|
elementWriter.WriteStartElement(prefixBuffer, prefixOffset, prefixLength, localNameBuffer, localNameOffset, localNameLength);
|
|
|
|
// If we have a inclusivenamespace prefix list and the namespace declaration is in the
|
|
// outer context, then Add it to the root element.
|
|
if (isRootElement && (this.inclusivePrefixes != null))
|
|
{
|
|
// Scan through all the namespace declarations in the outer scope.
|
|
for (int i = 0; i < this.scopes[0].xmlnsAttributeCount; ++i)
|
|
{
|
|
if (IsInclusivePrefix(ref xmlnsAttributes[i]))
|
|
{
|
|
XmlnsAttribute attribute = xmlnsAttributes[i];
|
|
AddXmlnsAttribute(ref attribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool IsInclusivePrefix(ref XmlnsAttribute xmlnsAttribute)
|
|
{
|
|
for (int i = 0; i < this.inclusivePrefixes.Length; ++i)
|
|
{
|
|
if (this.inclusivePrefixes[i].Length == xmlnsAttribute.prefixLength)
|
|
{
|
|
if (String.Compare(Encoding.UTF8.GetString(xmlnsBuffer, xmlnsAttribute.prefixOffset, xmlnsAttribute.prefixLength), this.inclusivePrefixes[i], StringComparison.Ordinal) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void WriteEndStartElement(bool isEmpty)
|
|
{
|
|
ThrowIfClosed();
|
|
elementWriter.Flush();
|
|
elementBuffer = elementStream.GetBuffer();
|
|
inStartElement = false;
|
|
ResolvePrefixes();
|
|
writer.WriteStartElement(elementBuffer, element.prefixOffset, element.prefixLength, elementBuffer, element.localNameOffset, element.localNameLength);
|
|
for (int i = scopes[depth - 1].xmlnsAttributeCount; i < xmlnsAttributeCount; i++)
|
|
{
|
|
// Check if this prefix with the same namespace has already been rendered.
|
|
int j = i - 1;
|
|
bool alreadyReferred = false;
|
|
while (j >= 0)
|
|
{
|
|
if (Equals(xmlnsBuffer, xmlnsAttributes[i].prefixOffset, xmlnsAttributes[i].prefixLength, xmlnsBuffer, xmlnsAttributes[j].prefixOffset, xmlnsAttributes[j].prefixLength))
|
|
{
|
|
// Check if the namespace is also equal.
|
|
if (Equals(xmlnsBuffer, xmlnsAttributes[i].nsOffset, xmlnsAttributes[i].nsLength, xmlnsBuffer, xmlnsAttributes[j].nsOffset, xmlnsAttributes[j].nsLength))
|
|
{
|
|
// We have found the prefix with the same namespace occur before. See if this has been
|
|
// referred.
|
|
if (xmlnsAttributes[j].referred)
|
|
{
|
|
// This has been referred previously. So we don't have
|
|
// to output the namespace again.
|
|
alreadyReferred = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The prefix is the same, but the namespace value has changed. So we have to
|
|
// output this namespace.
|
|
break;
|
|
}
|
|
}
|
|
--j;
|
|
}
|
|
|
|
if (!alreadyReferred)
|
|
{
|
|
WriteXmlnsAttribute(ref xmlnsAttributes[i]);
|
|
}
|
|
}
|
|
if (attributeCount > 0)
|
|
{
|
|
if (attributeCount > 1)
|
|
{
|
|
SortAttributes();
|
|
}
|
|
|
|
for (int i = 0; i < attributeCount; i++)
|
|
{
|
|
writer.WriteText(elementBuffer, attributes[i].offset, attributes[i].length);
|
|
}
|
|
}
|
|
writer.WriteEndStartElement(false);
|
|
if (isEmpty)
|
|
{
|
|
writer.WriteEndElement(elementBuffer, element.prefixOffset, element.prefixLength, elementBuffer, element.localNameOffset, element.localNameLength);
|
|
EndElement();
|
|
}
|
|
elementBuffer = null;
|
|
}
|
|
|
|
public void WriteEndElement(string prefix, string localName)
|
|
{
|
|
if (prefix == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("prefix");
|
|
if (localName == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
|
|
ThrowIfClosed();
|
|
writer.WriteEndElement(prefix, localName);
|
|
EndElement();
|
|
}
|
|
|
|
void EnsureXmlnsBuffer(int byteCount)
|
|
{
|
|
if (xmlnsBuffer == null)
|
|
{
|
|
xmlnsBuffer = new byte[Math.Max(byteCount, 128)];
|
|
}
|
|
else if (xmlnsOffset + byteCount > xmlnsBuffer.Length)
|
|
{
|
|
byte[] newBuffer = new byte[Math.Max(xmlnsOffset + byteCount, xmlnsBuffer.Length * 2)];
|
|
Buffer.BlockCopy(xmlnsBuffer, 0, newBuffer, 0, xmlnsOffset);
|
|
xmlnsBuffer = newBuffer;
|
|
}
|
|
}
|
|
|
|
public void WriteXmlnsAttribute(string prefix, string ns)
|
|
{
|
|
if (prefix == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("prefix");
|
|
if (ns == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("ns");
|
|
ThrowIfClosed();
|
|
if (prefix.Length > int.MaxValue - ns.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("ns", SR.GetString(SR.CombinedPrefixNSLength, int.MaxValue / maxBytesPerChar)));
|
|
int totalLength = prefix.Length + ns.Length;
|
|
if (totalLength > int.MaxValue / maxBytesPerChar)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("ns", SR.GetString(SR.CombinedPrefixNSLength, int.MaxValue / maxBytesPerChar)));
|
|
EnsureXmlnsBuffer(totalLength * maxBytesPerChar);
|
|
XmlnsAttribute xmlnsAttribute;
|
|
xmlnsAttribute.prefixOffset = xmlnsOffset;
|
|
xmlnsAttribute.prefixLength = Encoding.UTF8.GetBytes(prefix, 0, prefix.Length, xmlnsBuffer, xmlnsOffset);
|
|
xmlnsOffset += xmlnsAttribute.prefixLength;
|
|
xmlnsAttribute.nsOffset = xmlnsOffset;
|
|
xmlnsAttribute.nsLength = Encoding.UTF8.GetBytes(ns, 0, ns.Length, xmlnsBuffer, xmlnsOffset);
|
|
xmlnsOffset += xmlnsAttribute.nsLength;
|
|
xmlnsAttribute.referred = false;
|
|
AddXmlnsAttribute(ref xmlnsAttribute);
|
|
}
|
|
|
|
public void WriteXmlnsAttribute(byte[] prefixBuffer, int prefixOffset, int prefixLength, byte[] nsBuffer, int nsOffset, int nsLength)
|
|
{
|
|
if (prefixBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("prefixBuffer"));
|
|
if (prefixOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixOffset > prefixBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.OffsetExceedsBufferSize, prefixBuffer.Length)));
|
|
if (prefixLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixLength > prefixBuffer.Length - prefixOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, prefixBuffer.Length - prefixOffset)));
|
|
|
|
if (nsBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("nsBuffer"));
|
|
if (nsOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("nsOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (nsOffset > nsBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("nsOffset", SR.GetString(SR.OffsetExceedsBufferSize, nsBuffer.Length)));
|
|
if (nsLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("nsLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (nsLength > nsBuffer.Length - nsOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("nsLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, nsBuffer.Length - nsOffset)));
|
|
ThrowIfClosed();
|
|
if (prefixLength > int.MaxValue - nsLength)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("nsLength", SR.GetString(SR.CombinedPrefixNSLength, int.MaxValue)));
|
|
EnsureXmlnsBuffer(prefixLength + nsLength);
|
|
XmlnsAttribute xmlnsAttribute;
|
|
xmlnsAttribute.prefixOffset = xmlnsOffset;
|
|
xmlnsAttribute.prefixLength = prefixLength;
|
|
Buffer.BlockCopy(prefixBuffer, prefixOffset, xmlnsBuffer, xmlnsOffset, prefixLength);
|
|
xmlnsOffset += prefixLength;
|
|
xmlnsAttribute.nsOffset = xmlnsOffset;
|
|
xmlnsAttribute.nsLength = nsLength;
|
|
Buffer.BlockCopy(nsBuffer, nsOffset, xmlnsBuffer, xmlnsOffset, nsLength);
|
|
xmlnsOffset += nsLength;
|
|
xmlnsAttribute.referred = false;
|
|
AddXmlnsAttribute(ref xmlnsAttribute);
|
|
}
|
|
|
|
public void WriteStartAttribute(string prefix, string localName)
|
|
{
|
|
if (prefix == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("prefix");
|
|
if (localName == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("localName");
|
|
ThrowIfClosed();
|
|
attribute.offset = elementWriter.Position;
|
|
attribute.length = 0;
|
|
attribute.prefixOffset = attribute.offset + 1; // WriteStartAttribute emits a space
|
|
attribute.prefixLength = Encoding.UTF8.GetByteCount(prefix);
|
|
attribute.localNameOffset = attribute.prefixOffset + attribute.prefixLength + (attribute.prefixLength != 0 ? 1 : 0);
|
|
attribute.localNameLength = Encoding.UTF8.GetByteCount(localName);
|
|
attribute.nsOffset = 0;
|
|
attribute.nsLength = 0;
|
|
elementWriter.WriteStartAttribute(prefix, localName);
|
|
}
|
|
|
|
public void WriteStartAttribute(byte[] prefixBuffer, int prefixOffset, int prefixLength, byte[] localNameBuffer, int localNameOffset, int localNameLength)
|
|
{
|
|
if (prefixBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("prefixBuffer"));
|
|
if (prefixOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixOffset > prefixBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixOffset", SR.GetString(SR.OffsetExceedsBufferSize, prefixBuffer.Length)));
|
|
if (prefixLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (prefixLength > prefixBuffer.Length - prefixOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("prefixLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, prefixBuffer.Length - prefixOffset)));
|
|
|
|
if (localNameBuffer == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("localNameBuffer"));
|
|
if (localNameOffset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameOffset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (localNameOffset > localNameBuffer.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameOffset", SR.GetString(SR.OffsetExceedsBufferSize, localNameBuffer.Length)));
|
|
if (localNameLength < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameLength", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (localNameLength > localNameBuffer.Length - localNameOffset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("localNameLength", SR.GetString(SR.SizeExceedsRemainingBufferSpace, localNameBuffer.Length - localNameOffset)));
|
|
ThrowIfClosed();
|
|
attribute.offset = elementWriter.Position;
|
|
attribute.length = 0;
|
|
attribute.prefixOffset = attribute.offset + 1; // WriteStartAttribute emits a space
|
|
attribute.prefixLength = prefixLength;
|
|
attribute.localNameOffset = attribute.prefixOffset + prefixLength + (prefixLength != 0 ? 1 : 0);
|
|
attribute.localNameLength = localNameLength;
|
|
attribute.nsOffset = 0;
|
|
attribute.nsLength = 0;
|
|
elementWriter.WriteStartAttribute(prefixBuffer, prefixOffset, prefixLength, localNameBuffer, localNameOffset, localNameLength);
|
|
}
|
|
|
|
public void WriteEndAttribute()
|
|
{
|
|
ThrowIfClosed();
|
|
elementWriter.WriteEndAttribute();
|
|
attribute.length = elementWriter.Position - attribute.offset;
|
|
AddAttribute(ref attribute);
|
|
}
|
|
|
|
public void WriteCharEntity(int ch)
|
|
{
|
|
ThrowIfClosed();
|
|
if (ch <= char.MaxValue)
|
|
{
|
|
char[] chars = new char[1] { (char)ch };
|
|
WriteEscapedText(chars, 0, 1);
|
|
}
|
|
else
|
|
{
|
|
WriteText(ch);
|
|
}
|
|
}
|
|
|
|
public void WriteEscapedText(string value)
|
|
{
|
|
if (value == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
ThrowIfClosed();
|
|
// Skip all white spaces before the start of root element.
|
|
if (this.depth > 0)
|
|
{
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteEscapedText(value);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteEscapedText(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteEscapedText(byte[] chars, int offset, int count)
|
|
{
|
|
if (chars == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("chars"));
|
|
if (offset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (offset > chars.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, chars.Length)));
|
|
if (count < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (count > chars.Length - offset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset)));
|
|
ThrowIfClosed();
|
|
// Skip all white spaces before the start of root element.
|
|
if (this.depth > 0)
|
|
{
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteEscapedText(chars, offset, count);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteEscapedText(chars, offset, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteEscapedText(char[] chars, int offset, int count)
|
|
{
|
|
ThrowIfClosed();
|
|
// Skip all white spaces before the start of root element.
|
|
if (this.depth > 0)
|
|
{
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteEscapedText(chars, offset, count);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteEscapedText(chars, offset, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if OLDWRITER
|
|
unsafe internal void WriteText(char* chars, int charCount)
|
|
{
|
|
ThrowIfClosed();
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteText(chars, charCount);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteText(chars, charCount);
|
|
}
|
|
}
|
|
unsafe internal void WriteEscapedText(char* chars, int count)
|
|
{
|
|
ThrowIfClosed();
|
|
// Skip all white spaces before the start of root element.
|
|
if (this.depth > 0)
|
|
{
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteEscapedText(chars, count);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteEscapedText(chars, count);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public void WriteText(int ch)
|
|
{
|
|
ThrowIfClosed();
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteText(ch);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteText(ch);
|
|
}
|
|
}
|
|
|
|
public void WriteText(byte[] chars, int offset, int count)
|
|
{
|
|
ThrowIfClosed();
|
|
if (chars == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("chars"));
|
|
if (offset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (offset > chars.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, chars.Length)));
|
|
if (count < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (count > chars.Length - offset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset)));
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteText(chars, offset, count);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteText(chars, offset, count);
|
|
}
|
|
}
|
|
|
|
public void WriteText(string value)
|
|
{
|
|
if (value == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("value"));
|
|
if (value.Length > 0)
|
|
{
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteText(value);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteText(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteText(char[] chars, int offset, int count)
|
|
{
|
|
ThrowIfClosed();
|
|
if (chars == null)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("chars"));
|
|
if (offset < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (offset > chars.Length)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("offset", SR.GetString(SR.OffsetExceedsBufferSize, chars.Length)));
|
|
if (count < 0)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.ValueMustBeNonNegative)));
|
|
if (count > chars.Length - offset)
|
|
throw System.Runtime.Serialization.DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("count", SR.GetString(SR.SizeExceedsRemainingBufferSpace, chars.Length - offset)));
|
|
if (inStartElement)
|
|
{
|
|
elementWriter.WriteText(chars, offset, count);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteText(chars, offset, count);
|
|
}
|
|
}
|
|
|
|
void ThrowIfClosed()
|
|
{
|
|
if (writer == null)
|
|
ThrowClosed();
|
|
}
|
|
|
|
void ThrowClosed()
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(this.GetType().ToString()));
|
|
}
|
|
|
|
void WriteXmlnsAttribute(ref XmlnsAttribute xmlnsAttribute)
|
|
{
|
|
if (xmlnsAttribute.referred)
|
|
{
|
|
writer.WriteXmlnsAttribute(xmlnsBuffer, xmlnsAttribute.prefixOffset, xmlnsAttribute.prefixLength, xmlnsBuffer, xmlnsAttribute.nsOffset, xmlnsAttribute.nsLength);
|
|
}
|
|
}
|
|
|
|
void SortAttributes()
|
|
{
|
|
if (attributeCount < 16)
|
|
{
|
|
for (int i = 0; i < attributeCount - 1; i++)
|
|
{
|
|
int attributeMin = i;
|
|
for (int j = i + 1; j < attributeCount; j++)
|
|
{
|
|
if (Compare(ref attributes[j], ref attributes[attributeMin]) < 0)
|
|
{
|
|
attributeMin = j;
|
|
}
|
|
}
|
|
|
|
if (attributeMin != i)
|
|
{
|
|
Attribute temp = attributes[i];
|
|
attributes[i] = attributes[attributeMin];
|
|
attributes[attributeMin] = temp;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
new AttributeSorter(this).Sort();
|
|
}
|
|
}
|
|
|
|
void AddAttribute(ref Attribute attribute)
|
|
{
|
|
if (attributes == null)
|
|
{
|
|
attributes = new Attribute[4];
|
|
}
|
|
else if (attributeCount == attributes.Length)
|
|
{
|
|
Attribute[] newAttributes = new Attribute[attributeCount * 2];
|
|
Array.Copy(attributes, newAttributes, attributeCount);
|
|
attributes = newAttributes;
|
|
}
|
|
|
|
attributes[attributeCount] = attribute;
|
|
attributeCount++;
|
|
}
|
|
|
|
void AddXmlnsAttribute(ref XmlnsAttribute xmlnsAttribute)
|
|
{
|
|
// Console.WriteLine("{0}={1}", Encoding.UTF8.GetString(xmlnsBuffer, xmlnsAttribute.prefixOffset, xmlnsAttribute.prefixLength),
|
|
// Encoding.UTF8.GetString(xmlnsBuffer, xmlnsAttribute.nsOffset, xmlnsAttribute.nsLength));
|
|
|
|
if (xmlnsAttributes == null)
|
|
{
|
|
xmlnsAttributes = new XmlnsAttribute[4];
|
|
}
|
|
else if (xmlnsAttributes.Length == xmlnsAttributeCount)
|
|
{
|
|
XmlnsAttribute[] newXmlnsAttributes = new XmlnsAttribute[xmlnsAttributeCount * 2];
|
|
Array.Copy(xmlnsAttributes, newXmlnsAttributes, xmlnsAttributeCount);
|
|
xmlnsAttributes = newXmlnsAttributes;
|
|
}
|
|
|
|
// If the prefix is in the inclusive prefix list, then mark it as
|
|
// to be rendered. Depth 0 is outer context and those can be ignored
|
|
// for now.
|
|
if ((depth > 0) && (this.inclusivePrefixes != null))
|
|
{
|
|
if (IsInclusivePrefix(ref xmlnsAttribute))
|
|
{
|
|
xmlnsAttribute.referred = true;
|
|
}
|
|
}
|
|
|
|
if (depth == 0)
|
|
{
|
|
// XmlnsAttributes at depth 0 are the outer context. They don't need to be sorted.
|
|
xmlnsAttributes[xmlnsAttributeCount++] = xmlnsAttribute;
|
|
}
|
|
else
|
|
{
|
|
// Sort the xmlns xmlnsAttribute
|
|
int xmlnsAttributeIndex = scopes[depth - 1].xmlnsAttributeCount;
|
|
bool isNewPrefix = true;
|
|
while (xmlnsAttributeIndex < xmlnsAttributeCount)
|
|
{
|
|
int result = Compare(ref xmlnsAttribute, ref xmlnsAttributes[xmlnsAttributeIndex]);
|
|
if (result > 0)
|
|
{
|
|
xmlnsAttributeIndex++;
|
|
}
|
|
else if (result == 0)
|
|
{
|
|
// We already have the same prefix at this scope. So let's
|
|
// just replace the old one with the new.
|
|
xmlnsAttributes[xmlnsAttributeIndex] = xmlnsAttribute;
|
|
isNewPrefix = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isNewPrefix)
|
|
{
|
|
Array.Copy(xmlnsAttributes, xmlnsAttributeIndex, xmlnsAttributes, xmlnsAttributeIndex + 1, xmlnsAttributeCount - xmlnsAttributeIndex);
|
|
xmlnsAttributes[xmlnsAttributeIndex] = xmlnsAttribute;
|
|
xmlnsAttributeCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResolvePrefix(int prefixOffset, int prefixLength, out int nsOffset, out int nsLength)
|
|
{
|
|
int xmlnsAttributeMin = scopes[depth - 1].xmlnsAttributeCount;
|
|
|
|
// Lookup the attribute; it has to be there. The decls are in sorted order
|
|
// so we could do a binary search.
|
|
int j = xmlnsAttributeCount - 1;
|
|
while (!Equals(elementBuffer, prefixOffset, prefixLength,
|
|
xmlnsBuffer, xmlnsAttributes[j].prefixOffset, xmlnsAttributes[j].prefixLength))
|
|
{
|
|
j--;
|
|
}
|
|
|
|
nsOffset = xmlnsAttributes[j].nsOffset;
|
|
nsLength = xmlnsAttributes[j].nsLength;
|
|
|
|
if (j < xmlnsAttributeMin)
|
|
{
|
|
// If the xmlns decl isn't at this scope, see if we need to copy it down
|
|
if (!xmlnsAttributes[j].referred)
|
|
{
|
|
XmlnsAttribute xmlnsAttribute = xmlnsAttributes[j];
|
|
xmlnsAttribute.referred = true;
|
|
|
|
// This inserts the xmlns attribute in sorted order, so j is no longer valid
|
|
AddXmlnsAttribute(ref xmlnsAttribute);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Found at this scope, indicate we need to emit it
|
|
xmlnsAttributes[j].referred = true;
|
|
}
|
|
}
|
|
|
|
void ResolvePrefix(ref Attribute attribute)
|
|
{
|
|
if (attribute.prefixLength != 0)
|
|
{
|
|
ResolvePrefix(attribute.prefixOffset, attribute.prefixLength, out attribute.nsOffset, out attribute.nsLength);
|
|
}
|
|
else
|
|
{
|
|
// These should've been set when we added the prefix
|
|
Fx.Assert(attribute.nsOffset == 0 && attribute.nsLength == 0, "");
|
|
}
|
|
}
|
|
|
|
void ResolvePrefixes()
|
|
{
|
|
int nsOffset;
|
|
int nsLength;
|
|
ResolvePrefix(element.prefixOffset, element.prefixLength, out nsOffset, out nsLength);
|
|
|
|
for (int i = 0; i < attributeCount; i++)
|
|
{
|
|
ResolvePrefix(ref attributes[i]);
|
|
}
|
|
}
|
|
|
|
int Compare(ref XmlnsAttribute xmlnsAttribute1, ref XmlnsAttribute xmlnsAttribute2)
|
|
{
|
|
return Compare(xmlnsBuffer,
|
|
xmlnsAttribute1.prefixOffset, xmlnsAttribute1.prefixLength,
|
|
xmlnsAttribute2.prefixOffset, xmlnsAttribute2.prefixLength);
|
|
}
|
|
|
|
int Compare(ref Attribute attribute1, ref Attribute attribute2)
|
|
{
|
|
int s = Compare(xmlnsBuffer,
|
|
attribute1.nsOffset, attribute1.nsLength,
|
|
attribute2.nsOffset, attribute2.nsLength);
|
|
|
|
if (s == 0)
|
|
{
|
|
s = Compare(elementBuffer,
|
|
attribute1.localNameOffset, attribute1.localNameLength,
|
|
attribute2.localNameOffset, attribute2.localNameLength);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
int Compare(byte[] buffer, int offset1, int length1, int offset2, int length2)
|
|
{
|
|
if (offset1 == offset2)
|
|
{
|
|
return length1 - length2;
|
|
}
|
|
|
|
return Compare(buffer, offset1, length1, buffer, offset2, length2);
|
|
}
|
|
|
|
int Compare(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2)
|
|
{
|
|
// Console.WriteLine("Compare: \"{0}\", \"{1}\"", Encoding.UTF8.GetString(sourceBuffer, offset1, length1), Encoding.UTF8.GetString(sourceBuffer, offset2, length2));
|
|
|
|
int length = Math.Min(length1, length2);
|
|
|
|
int s = 0;
|
|
for (int i = 0; i < length && s == 0; i++)
|
|
{
|
|
s = buffer1[offset1 + i] - buffer2[offset2 + i];
|
|
}
|
|
|
|
if (s == 0)
|
|
{
|
|
s = length1 - length2;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
bool Equals(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2)
|
|
{
|
|
// Console.WriteLine("Equals: \"{0}\", \"{1}\"", Encoding.UTF8.GetString(buffer1, offset1, length1), Encoding.UTF8.GetString(buffer2, offset2, length2));
|
|
|
|
if (length1 != length2)
|
|
return false;
|
|
|
|
for (int i = 0; i < length1; i++)
|
|
{
|
|
if (buffer1[offset1 + i] != buffer2[offset2 + i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
class AttributeSorter : IComparer
|
|
{
|
|
XmlCanonicalWriter writer;
|
|
|
|
public AttributeSorter(XmlCanonicalWriter writer)
|
|
{
|
|
this.writer = writer;
|
|
}
|
|
|
|
public void Sort()
|
|
{
|
|
object[] indeces = new object[writer.attributeCount];
|
|
|
|
for (int i = 0; i < indeces.Length; i++)
|
|
{
|
|
indeces[i] = i;
|
|
}
|
|
|
|
Array.Sort(indeces, this);
|
|
|
|
Attribute[] attributes = new Attribute[writer.attributes.Length];
|
|
for (int i = 0; i < indeces.Length; i++)
|
|
{
|
|
attributes[i] = writer.attributes[(int)indeces[i]];
|
|
}
|
|
|
|
writer.attributes = attributes;
|
|
}
|
|
|
|
public int Compare(object obj1, object obj2)
|
|
{
|
|
int attributeIndex1 = (int)obj1;
|
|
int attributeIndex2 = (int)obj2;
|
|
return writer.Compare(ref writer.attributes[attributeIndex1], ref writer.attributes[attributeIndex2]);
|
|
}
|
|
}
|
|
|
|
struct Scope
|
|
{
|
|
public int xmlnsAttributeCount;
|
|
public int xmlnsOffset;
|
|
}
|
|
|
|
struct Element
|
|
{
|
|
public int prefixOffset;
|
|
public int prefixLength;
|
|
public int localNameOffset;
|
|
public int localNameLength;
|
|
}
|
|
|
|
struct Attribute
|
|
{
|
|
public int prefixOffset;
|
|
public int prefixLength;
|
|
public int localNameOffset;
|
|
public int localNameLength;
|
|
public int nsOffset;
|
|
public int nsLength;
|
|
public int offset;
|
|
public int length;
|
|
}
|
|
|
|
struct XmlnsAttribute
|
|
{
|
|
public int prefixOffset;
|
|
public int prefixLength;
|
|
public int nsOffset;
|
|
public int nsLength;
|
|
public bool referred;
|
|
}
|
|
}
|
|
}
|