| #region Apache License |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to you under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| #endregion |
| |
| using System; |
| using System.Text; |
| using System.Xml; |
| using System.Text.RegularExpressions; |
| |
| namespace log4net.Util |
| { |
| /// <summary> |
| /// Utility class for transforming strings. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Utility class for transforming strings. |
| /// </para> |
| /// </remarks> |
| /// <author>Nicko Cadell</author> |
| /// <author>Gert Driesen</author> |
| public sealed class Transform |
| { |
| #region Private Instance Constructors |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="Transform" /> class. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Uses a private access modifier to prevent instantiation of this class. |
| /// </para> |
| /// </remarks> |
| private Transform() |
| { |
| } |
| |
| #endregion Private Instance Constructors |
| |
| #region XML String Methods |
| |
| /// <summary> |
| /// Write a string to an <see cref="XmlWriter"/> |
| /// </summary> |
| /// <param name="writer">the writer to write to</param> |
| /// <param name="textData">the string to write</param> |
| /// <param name="invalidCharReplacement">The string to replace non XML compliant chars with</param> |
| /// <remarks> |
| /// <para> |
| /// The test is escaped either using XML escape entities |
| /// or using CDATA sections. |
| /// </para> |
| /// </remarks> |
| public static void WriteEscapedXmlString(XmlWriter writer, string textData, string invalidCharReplacement) |
| { |
| string stringData = MaskXmlInvalidCharacters(textData, invalidCharReplacement); |
| // Write either escaped text or CDATA sections |
| |
| int weightCData = 12 * (1 + CountSubstrings(stringData, CDATA_END)); |
| int weightStringEscapes = 3 * (CountSubstrings(stringData, "<") + CountSubstrings(stringData, ">")) + 4 * CountSubstrings(stringData, "&"); |
| |
| if (weightStringEscapes <= weightCData) |
| { |
| // Write string using string escapes |
| writer.WriteString(stringData); |
| } |
| else |
| { |
| // Write string using CDATA section |
| |
| int end = stringData.IndexOf(CDATA_END); |
| |
| if (end < 0) |
| { |
| writer.WriteCData(stringData); |
| } |
| else |
| { |
| int start = 0; |
| while (end > -1) |
| { |
| writer.WriteCData(stringData.Substring(start, end - start)); |
| if (end == stringData.Length - 3) |
| { |
| start = stringData.Length; |
| writer.WriteString(CDATA_END); |
| break; |
| } |
| else |
| { |
| writer.WriteString(CDATA_UNESCAPABLE_TOKEN); |
| start = end + 2; |
| end = stringData.IndexOf(CDATA_END, start); |
| } |
| } |
| |
| if (start < stringData.Length) |
| { |
| writer.WriteCData(stringData.Substring(start)); |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Replace invalid XML characters in text string |
| /// </summary> |
| /// <param name="textData">the XML text input string</param> |
| /// <param name="mask">the string to use in place of invalid characters</param> |
| /// <returns>A string that does not contain invalid XML characters.</returns> |
| /// <remarks> |
| /// <para> |
| /// Certain Unicode code points are not allowed in the XML InfoSet, for |
| /// details see: <a href="http://www.w3.org/TR/REC-xml/#charsets">http://www.w3.org/TR/REC-xml/#charsets</a>. |
| /// </para> |
| /// <para> |
| /// This method replaces any illegal characters in the input string |
| /// with the mask string specified. |
| /// </para> |
| /// </remarks> |
| public static string MaskXmlInvalidCharacters(string textData, string mask) |
| { |
| return INVALIDCHARS.Replace(textData, mask); |
| } |
| |
| #endregion XML String Methods |
| |
| #region Private Helper Methods |
| |
| /// <summary> |
| /// Count the number of times that the substring occurs in the text |
| /// </summary> |
| /// <param name="text">the text to search</param> |
| /// <param name="substring">the substring to find</param> |
| /// <returns>the number of times the substring occurs in the text</returns> |
| /// <remarks> |
| /// <para> |
| /// The substring is assumed to be non repeating within itself. |
| /// </para> |
| /// </remarks> |
| private static int CountSubstrings(string text, string substring) |
| { |
| int count = 0; |
| int offset = 0; |
| int length = text.Length; |
| int substringLength = substring.Length; |
| |
| if (length == 0) |
| { |
| return 0; |
| } |
| if (substringLength == 0) |
| { |
| return 0; |
| } |
| |
| while (offset < length) |
| { |
| int index = text.IndexOf(substring, offset); |
| |
| if (index == -1) |
| { |
| break; |
| } |
| |
| count++; |
| offset = index + substringLength; |
| } |
| return count; |
| } |
| |
| #endregion |
| |
| #region Private Static Fields |
| |
| private const string CDATA_END = "]]>"; |
| private const string CDATA_UNESCAPABLE_TOKEN = "]]"; |
| |
| /// <summary> |
| /// Characters illegal in XML 1.0 |
| /// </summary> |
| private static Regex INVALIDCHARS = new Regex(@"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD]", RegexOptions.Compiled); |
| #endregion Private Static Fields |
| } |
| } |