|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Globalization;
namespace Microsoft.Xml
{
using System;
// XmlTextEncoder
//
// This class does special handling of text content for XML. For example
// it will replace special characters with entities whenever necessary.
internal class XmlTextEncoder
{
//
// Fields
//
// output text writer
private TextWriter _textWriter;
// true when writing out the content of attribute value
private bool _inAttribute;
// quote char of the attribute (when inAttribute)
private char _quoteChar;
// caching of attribute value
private StringBuilder _attrValue;
private bool _cacheAttrValue;
// XmlCharType
private XmlCharType _xmlCharType;
//
// Constructor
//
internal XmlTextEncoder(TextWriter textWriter)
{
_textWriter = textWriter;
_quoteChar = '"';
_xmlCharType = XmlCharType.Instance;
}
//
// Internal methods and properties
//
internal char QuoteChar
{
set
{
_quoteChar = value;
}
}
internal void StartAttribute(bool cacheAttrValue)
{
_inAttribute = true;
_cacheAttrValue = cacheAttrValue;
if (cacheAttrValue)
{
if (_attrValue == null)
{
_attrValue = new StringBuilder();
}
else
{
_attrValue.Length = 0;
}
}
}
internal void EndAttribute()
{
if (_cacheAttrValue)
{
_attrValue.Length = 0;
}
_inAttribute = false;
_cacheAttrValue = false;
}
internal string AttributeValue
{
get
{
if (_cacheAttrValue)
{
return _attrValue.ToString();
}
else
{
return String.Empty;
}
}
}
internal void WriteSurrogateChar(char lowChar, char highChar)
{
if (!XmlCharType.IsLowSurrogate(lowChar) ||
!XmlCharType.IsHighSurrogate(highChar))
{
throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, highChar);
}
_textWriter.Write(highChar);
_textWriter.Write(lowChar);
}
#if FEATURE_NETCORE
[System.Security.SecurityCritical]
#endif
internal void Write(char[] array, int offset, int count)
{
if (null == array)
{
throw new ArgumentNullException("array");
}
if (0 > offset)
{
throw new ArgumentOutOfRangeException("offset");
}
if (0 > count)
{
throw new ArgumentOutOfRangeException("count");
}
if (count > array.Length - offset)
{
throw new ArgumentOutOfRangeException("count");
}
if (_cacheAttrValue)
{
_attrValue.Append(array, offset, count);
}
int endPos = offset + count;
int i = offset;
char ch = (char)0;
for (; ; )
{
int startPos = i;
unsafe
{
while (i < endPos && (_xmlCharType.charProperties[ch = array[i]] & XmlCharType.fAttrValue) != 0)
{ // ( xmlCharType.IsAttributeValueChar( ( ch = array[i] ) ) ) ) {
i++;
}
}
if (startPos < i)
{
_textWriter.Write(array, startPos, i - startPos);
}
if (i == endPos)
{
break;
}
switch (ch)
{
case (char)0x9:
_textWriter.Write(ch);
break;
case (char)0xA:
case (char)0xD:
if (_inAttribute)
{
WriteCharEntityImpl(ch);
}
else
{
_textWriter.Write(ch);
}
break;
case '<':
WriteEntityRefImpl("lt");
break;
case '>':
WriteEntityRefImpl("gt");
break;
case '&':
WriteEntityRefImpl("amp");
break;
case '\'':
if (_inAttribute && _quoteChar == ch)
{
WriteEntityRefImpl("apos");
}
else
{
_textWriter.Write('\'');
}
break;
case '"':
if (_inAttribute && _quoteChar == ch)
{
WriteEntityRefImpl("quot");
}
else
{
_textWriter.Write('"');
}
break;
default:
if (XmlCharType.IsHighSurrogate(ch))
{
if (i + 1 < endPos)
{
WriteSurrogateChar(array[++i], ch);
}
else
{
throw new ArgumentException(ResXml.Xml_SurrogatePairSplit);
}
}
else if (XmlCharType.IsLowSurrogate(ch))
{
throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
}
else
{
Debug.Assert((ch < 0x20 && !_xmlCharType.IsWhiteSpace(ch)) || (ch > 0xFFFD));
WriteCharEntityImpl(ch);
}
break;
}
i++;
}
}
internal void WriteSurrogateCharEntity(char lowChar, char highChar)
{
if (!XmlCharType.IsLowSurrogate(lowChar) ||
!XmlCharType.IsHighSurrogate(highChar))
{
throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, highChar);
}
int surrogateChar = XmlCharType.CombineSurrogateChar(lowChar, highChar);
if (_cacheAttrValue)
{
_attrValue.Append(highChar);
_attrValue.Append(lowChar);
}
_textWriter.Write("&#x");
_textWriter.Write(surrogateChar.ToString("X", NumberFormatInfo.InvariantInfo));
_textWriter.Write(';');
}
#if FEATURE_NETCORE
[System.Security.SecurityCritical]
#endif
internal void Write(string text)
{
if (text == null)
{
return;
}
if (_cacheAttrValue)
{
_attrValue.Append(text);
}
// scan through the string to see if there are any characters to be escaped
int len = text.Length;
int i = 0;
int startPos = 0;
char ch = (char)0;
for (; ; )
{
unsafe
{
while (i < len && (_xmlCharType.charProperties[ch = text[i]] & XmlCharType.fAttrValue) != 0)
{ // ( xmlCharType.IsAttributeValueChar( ( ch = text[i] ) ) ) ) {
i++;
}
}
if (i == len)
{
// reached the end of the string -> write it whole out
_textWriter.Write(text);
return;
}
if (_inAttribute)
{
if (ch == 0x9)
{
i++;
continue;
}
}
else
{
if (ch == 0x9 || ch == 0xA || ch == 0xD || ch == '"' || ch == '\'')
{
i++;
continue;
}
}
// some character that needs to be escaped is found:
break;
}
char[] helperBuffer = new char[256];
for (; ; )
{
if (startPos < i)
{
WriteStringFragment(text, startPos, i - startPos, helperBuffer);
}
if (i == len)
{
break;
}
switch (ch)
{
case (char)0x9:
_textWriter.Write(ch);
break;
case (char)0xA:
case (char)0xD:
if (_inAttribute)
{
WriteCharEntityImpl(ch);
}
else
{
_textWriter.Write(ch);
}
break;
case '<':
WriteEntityRefImpl("lt");
break;
case '>':
WriteEntityRefImpl("gt");
break;
case '&':
WriteEntityRefImpl("amp");
break;
case '\'':
if (_inAttribute && _quoteChar == ch)
{
WriteEntityRefImpl("apos");
}
else
{
_textWriter.Write('\'');
}
break;
case '"':
if (_inAttribute && _quoteChar == ch)
{
WriteEntityRefImpl("quot");
}
else
{
_textWriter.Write('"');
}
break;
default:
if (XmlCharType.IsHighSurrogate(ch))
{
if (i + 1 < len)
{
WriteSurrogateChar(text[++i], ch);
}
else
{
throw XmlConvert.CreateInvalidSurrogatePairException(text[i], ch);
}
}
else if (XmlCharType.IsLowSurrogate(ch))
{
throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
}
else
{
Debug.Assert((ch < 0x20 && !_xmlCharType.IsWhiteSpace(ch)) || (ch > 0xFFFD));
WriteCharEntityImpl(ch);
}
break;
}
i++;
startPos = i;
unsafe
{
while (i < len && (_xmlCharType.charProperties[ch = text[i]] & XmlCharType.fAttrValue) != 0)
{ // ( xmlCharType.IsAttributeValueChar( ( text[i] ) ) ) ) {
i++;
}
}
}
}
#if FEATURE_NETCORE
[System.Security.SecurityCritical]
#endif
internal void WriteRawWithSurrogateChecking(string text)
{
if (text == null)
{
return;
}
if (_cacheAttrValue)
{
_attrValue.Append(text);
}
int len = text.Length;
int i = 0;
char ch = (char)0;
for (; ; )
{
unsafe
{
while (i < len &&
((_xmlCharType.charProperties[ch = text[i]] & XmlCharType.fCharData) != 0 // ( xmlCharType.IsCharData( ( ch = text[i] ) )
|| ch < 0x20))
{
i++;
}
}
if (i == len)
{
break;
}
if (XmlCharType.IsHighSurrogate(ch))
{
if (i + 1 < len)
{
char lowChar = text[i + 1];
if (XmlCharType.IsLowSurrogate(lowChar))
{
i += 2;
continue;
}
else
{
throw XmlConvert.CreateInvalidSurrogatePairException(lowChar, ch);
}
}
throw new ArgumentException(ResXml.Xml_InvalidSurrogateMissingLowChar);
}
else if (XmlCharType.IsLowSurrogate(ch))
{
throw XmlConvert.CreateInvalidHighSurrogateCharException(ch);
}
else
{
i++;
}
}
_textWriter.Write(text);
return;
}
internal void WriteRaw(string value)
{
if (_cacheAttrValue)
{
_attrValue.Append(value);
}
_textWriter.Write(value);
}
internal void WriteRaw(char[] array, int offset, int count)
{
if (null == array)
{
throw new ArgumentNullException("array");
}
if (0 > count)
{
throw new ArgumentOutOfRangeException("count");
}
if (0 > offset)
{
throw new ArgumentOutOfRangeException("offset");
}
if (count > array.Length - offset)
{
throw new ArgumentOutOfRangeException("count");
}
if (_cacheAttrValue)
{
_attrValue.Append(array, offset, count);
}
_textWriter.Write(array, offset, count);
}
internal void WriteCharEntity(char ch)
{
if (XmlCharType.IsSurrogate(ch))
{
throw new ArgumentException(ResXml.Xml_InvalidSurrogateMissingLowChar);
}
string strVal = ((int)ch).ToString("X", NumberFormatInfo.InvariantInfo);
if (_cacheAttrValue)
{
_attrValue.Append("&#x");
_attrValue.Append(strVal);
_attrValue.Append(';');
}
WriteCharEntityImpl(strVal);
}
internal void WriteEntityRef(string name)
{
if (_cacheAttrValue)
{
_attrValue.Append('&');
_attrValue.Append(name);
_attrValue.Append(';');
}
WriteEntityRefImpl(name);
}
internal void Flush()
{
// TODO?
}
//
// Private implementation methods
//
// This is a helper method to woraround the fact that TextWriter does not have a Write method
// for fragment of a string such as Write( string, offset, count).
// The string fragment will be written out by copying into a small helper buffer and then
// calling textWriter to write out the buffer.
private void WriteStringFragment(string str, int offset, int count, char[] helperBuffer)
{
int bufferSize = helperBuffer.Length;
while (count > 0)
{
int copyCount = count;
if (copyCount > bufferSize)
{
copyCount = bufferSize;
}
str.CopyTo(offset, helperBuffer, 0, copyCount);
_textWriter.Write(helperBuffer, 0, copyCount);
offset += copyCount;
count -= copyCount;
}
}
private void WriteCharEntityImpl(char ch)
{
WriteCharEntityImpl(((int)ch).ToString("X", NumberFormatInfo.InvariantInfo));
}
private void WriteCharEntityImpl(string strVal)
{
_textWriter.Write("&#x");
_textWriter.Write(strVal);
_textWriter.Write(';');
}
private void WriteEntityRefImpl(string name)
{
_textWriter.Write('&');
_textWriter.Write(name);
_textWriter.Write(';');
}
}
}
|