diff --git a/main/SS/Format/CellDateFormatter.cs b/main/SS/Format/CellDateFormatter.cs index a1d3f21fe..dd5bf98e4 100644 --- a/main/SS/Format/CellDateFormatter.cs +++ b/main/SS/Format/CellDateFormatter.cs @@ -20,6 +20,7 @@ limitations under the License. using System.Text; using NPOI.SS.Util; using System.Globalization; +using NPOI.Util; namespace NPOI.SS.Format { @@ -159,6 +160,18 @@ public void Finish(StringBuilder toAppendTo) * @param format The format. */ public CellDateFormatter(String format) + : this(LocaleUtil.GetUserLocale(), format) + { + + } + + /** + * Creates a new date formatter with the given specification. + * + * @param locale The locale. + * @param format The format. + */ + public CellDateFormatter(CultureInfo locale, String format) : base(format) { DatePartHandler partHandler = new DatePartHandler(this); @@ -171,7 +184,7 @@ public CellDateFormatter(String format) // See https://issues.apache.org/bugzilla/show_bug.cgi?id=53369 String ptrn = Regex.Replace(descBuf.ToString(), "((y)(?!y))(?General. */ public class GeneralCellFormat : CellFormat { - public GeneralCellFormat() - : base("General") + public GeneralCellFormat(CultureInfo locale) + : base(locale, "General") { } public override CellFormatResult Apply(Object value) { - String text = (new CellGeneralFormatter()).Format(value); + String text = (new CellGeneralFormatter(locale)).Format(value); return new CellFormatResult(true, text, POIUtils.Color_Empty); } } + private static GeneralCellFormat CreateGeneralFormat(CultureInfo locale) + { + return new GeneralCellFormat(locale); + } /** Maps a format string to its Parsed version for efficiencies sake. */ - private static readonly Dictionary formatCache = - new Dictionary(); + private static Dictionary> formatCache = + new Dictionary>(); /** * Returns a {@link CellFormat} that applies the given format. Two calls @@ -130,28 +132,48 @@ public override CellFormatResult Apply(Object value) */ public static CellFormat GetInstance(String format) { + return GetInstance(LocaleUtil.GetUserLocale(), format); + } + /** + * Returns a {@link CellFormat} that applies the given format. Two calls + * with the same format may or may not return the same object. + * + * @param locale The locale. + * @param format The format. + * + * @return A {@link CellFormat} that applies the given format. + */ + public static CellFormat GetInstance(CultureInfo locale, String format) + { + Dictionary formatMap = formatCache.TryGetValue(locale, out Dictionary value) ? value : null; + if (formatMap == null) + { + formatMap = new Dictionary(); + formatCache[locale] = formatMap; + } CellFormat fmt = null; - if (formatCache.TryGetValue(format, out CellFormat value)) - fmt = value; + if (formatMap.TryGetValue(format, out CellFormat value1)) + fmt = value1; if (fmt == null) { if (format.Equals("General") || format.Equals("@")) - fmt = GENERAL_FORMAT; + fmt = CreateGeneralFormat(locale); else - fmt = new CellFormat(format); - formatCache.Add(format, fmt); + fmt = new CellFormat(locale, format); + formatMap.Add(format, fmt); } return fmt; } - /** * Creates a new object. * * @param format The format. */ - private CellFormat(String format) + private CellFormat(CultureInfo locale, String format) { + this.locale = locale; this.format = format; + CellFormatPart defaultTextFormat = new CellFormatPart(locale, "@"); MatchCollection mc = ONE_PART.Matches(format); List parts = new List(); @@ -166,7 +188,7 @@ private CellFormat(String format) if (valueDesc.EndsWith(';')) valueDesc = valueDesc.Substring(0, valueDesc.Length - 1); - parts.Add(new CellFormatPart(valueDesc)); + parts.Add(new CellFormatPart(locale, valueDesc)); } catch { @@ -182,19 +204,19 @@ private CellFormat(String format) posNumFmt = parts[(0)]; negNumFmt = null; zeroNumFmt = null; - textFmt = DEFAULT_TEXT_FORMAT; + textFmt = defaultTextFormat; break; case 2: posNumFmt = parts[0]; negNumFmt = parts[1]; zeroNumFmt = null; - textFmt = DEFAULT_TEXT_FORMAT; + textFmt = defaultTextFormat; break; case 3: posNumFmt = parts[0]; negNumFmt = parts[1]; zeroNumFmt = parts[2]; - textFmt = DEFAULT_TEXT_FORMAT; + textFmt = defaultTextFormat; break; case 4: default: @@ -335,7 +357,7 @@ private CellFormatPart GetApplicableFormatPart(Object value) } else { - return new CellFormatPart("General"); + return new CellFormatPart(locale, "General"); } } else if (formatPartCount == 2) diff --git a/main/SS/Format/CellFormatPart.cs b/main/SS/Format/CellFormatPart.cs index b559296e3..a5038d7c0 100644 --- a/main/SS/Format/CellFormatPart.cs +++ b/main/SS/Format/CellFormatPart.cs @@ -22,8 +22,8 @@ namespace NPOI.SS.Format using System.Collections.Generic; using System.Collections; using System.Text.RegularExpressions; - using System.Text; -using Cysharp.Text; + using System.Text;using Cysharp.Text; + using System.Globalization; using SixLabors.ImageSharp; using NPOI.Util; @@ -196,6 +196,16 @@ String HandlePart(Match m, String part, CellFormatType type, * @param desc The string to Parse. */ public CellFormatPart(String desc) + : this(LocaleUtil.GetUserLocale(), desc) + { + } + /** + * Create an object to represent a format part. + * + * @param locale The locale to use. + * @param desc The string to parse. + */ + public CellFormatPart(CultureInfo locale, String desc) { Match m = FORMAT_PAT.Match(desc); if (!m.Success) @@ -205,7 +215,7 @@ public CellFormatPart(String desc) color = GetColor(m); condition = GetCondition(m); type = GetCellFormatType(m); - format = GetFormatter(m); + format = GetFormatter(locale, m); } /** @@ -322,7 +332,7 @@ private static CellFormatType GetCellFormatType(Match matcher) * * @return The formatter. */ - private CellFormatter GetFormatter(Match matcher) + private CellFormatter GetFormatter(CultureInfo locale, Match matcher) { String fdesc = matcher.Groups[(SPECIFICATION_GROUP)].Value; // For now, we don't support localised currencies, so simplify if there @@ -344,7 +354,7 @@ private CellFormatter GetFormatter(Match matcher) } // Build a formatter for this simplified string - return type.Formatter(fdesc); + return type.Formatter(locale, fdesc); } /** diff --git a/main/SS/Format/CellFormatType.cs b/main/SS/Format/CellFormatType.cs index 71a1b7a3e..4c230ede1 100644 --- a/main/SS/Format/CellFormatType.cs +++ b/main/SS/Format/CellFormatType.cs @@ -16,6 +16,7 @@ limitations under the License. ==================================================================== */ using System; +using System.Globalization; namespace NPOI.SS.Format { @@ -29,6 +30,10 @@ public override bool IsSpecial(char ch) { return false; } + public override CellFormatter Formatter(CultureInfo locale, String pattern) + { + return new CellGeneralFormatter(locale); + } } internal sealed class NumberCellFormatType : CellFormatType @@ -41,6 +46,10 @@ public override bool IsSpecial(char ch) { return false; } + public override CellFormatter Formatter(CultureInfo locale, String pattern) + { + return new CellNumberFormatter(locale, pattern); + } } internal sealed class DateCellFormatType : CellFormatType @@ -53,6 +62,10 @@ public override CellFormatter Formatter(String pattern) { return new CellDateFormatter(pattern); } + public override CellFormatter Formatter(CultureInfo locale, String pattern) + { + return new CellDateFormatter(locale, pattern); + } } internal sealed class ElapsedCellFormatType : CellFormatType @@ -65,6 +78,10 @@ public override CellFormatter Formatter(String pattern) { return new CellElapsedFormatter(pattern); } + public override CellFormatter Formatter(CultureInfo locale, String pattern) + { + return new CellElapsedFormatter(pattern); + } } internal sealed class TextCellFormatType : CellFormatType @@ -77,6 +94,10 @@ public override CellFormatter Formatter(String pattern) { return new CellTextFormatter(pattern); } + public override CellFormatter Formatter(CultureInfo locale, String pattern) + { + return new CellTextFormatter(pattern); + } } /** @@ -115,5 +136,16 @@ public abstract class CellFormatType * @return A new formatter of the appropriate type, for the given pattern. */ public abstract CellFormatter Formatter(String pattern); + + /** + * Returns a new formatter of the appropriate type, for the given pattern. + * The pattern must be appropriate for the type. + * + * @param locale The locale to use. + * @param pattern The pattern to use. + * + * @return A new formatter of the appropriate type, for the given pattern. + */ + public abstract CellFormatter Formatter(CultureInfo locale, String pattern); } } \ No newline at end of file diff --git a/main/SS/Format/CellFormatter.cs b/main/SS/Format/CellFormatter.cs index 382ae8193..791605425 100644 --- a/main/SS/Format/CellFormatter.cs +++ b/main/SS/Format/CellFormatter.cs @@ -19,6 +19,7 @@ namespace NPOI.SS.Format using System; using System.Text; using System.Globalization; + using NPOI.Util; @@ -32,7 +33,7 @@ public abstract class CellFormatter { /** The original specified format. */ protected String format; - + protected CultureInfo locale; /** * This is the locale used to Get a consistent format result from which to * work. @@ -45,7 +46,19 @@ public abstract class CellFormatter * @param format The format. */ public CellFormatter(String format) + : this(LocaleUtil.GetUserLocale(), format) + { + } + + /** + * Creates a new formatter object, storing the format in {@link #format}. + * + * @param locale The locale. + * @param format The format. + */ + public CellFormatter(CultureInfo locale, String format) { + this.locale = locale; this.format = format; } diff --git a/main/SS/Format/CellGeneralFormatter.cs b/main/SS/Format/CellGeneralFormatter.cs index 930148c62..4a218cf0c 100644 --- a/main/SS/Format/CellGeneralFormatter.cs +++ b/main/SS/Format/CellGeneralFormatter.cs @@ -17,8 +17,10 @@ limitations under the License. namespace NPOI.SS.Format { using System; + using System.Globalization; + using System.Runtime.Serialization; using System.Text; - + using NPOI.Util; /** @@ -30,10 +32,15 @@ public class CellGeneralFormatter : CellFormatter { /** Creates a new general formatter. */ public CellGeneralFormatter() - : base("General") + : this(LocaleUtil.GetUserLocale()) { - ; } + /** Creates a new general formatter. */ + public CellGeneralFormatter(CultureInfo locale) + : base(locale, "General") + { + } + /** * The general style is not quite the same as any other, or any combination @@ -67,10 +74,8 @@ public override void FormatValue(StringBuilder toAppendTo, Object value) fmt = "F0"; stripZeros = false; } - toAppendTo.Append(val.ToString(fmt)); + toAppendTo.Append(val.ToString(fmt, locale)); - //Formatter formatter = new Formatter(toAppendTo); - //formatter.Format(LOCALE, fmt, value); if (stripZeros) { // strip off trailing zeros diff --git a/main/SS/Format/CellNumberFormatter.cs b/main/SS/Format/CellNumberFormatter.cs index e3fc934f4..c0ae34596 100644 --- a/main/SS/Format/CellNumberFormatter.cs +++ b/main/SS/Format/CellNumberFormatter.cs @@ -18,9 +18,10 @@ limitations under the License. using System; using System.Text; using System.Collections.Generic; - using NPOI.SS.Util; using System.Collections; +using System.Globalization; +using NPOI.Util; namespace NPOI.SS.Format { @@ -40,7 +41,7 @@ public class CellNumberFormatter : CellFormatter private Special numerator; private Special afterInteger; private Special afterFractional; - private bool integerCommas; + private bool showGroupingSeparator; private List specials = new List(); private List integerSpecials = new List(); private List fractionalSpecials = new List(); @@ -55,10 +56,9 @@ public class CellNumberFormatter : CellFormatter private DecimalFormat decimalFmt; private static List EmptySpecialList = new List(); - private static readonly SimpleNumberCellFormatter SIMPLE_NUMBER = new SimpleNumberCellFormatter("General"); - private static readonly CellNumberFormatter SIMPLE_INT = new CellNumberFormatter("#"); - private static readonly CellNumberFormatter SIMPLE_FLOAT = new CellNumberFormatter("#.#"); + + private readonly GeneralNumberFormatter SIMPLE_NUMBER; /// /// The CellNumberFormatter.simpleValue() method uses the SIMPLE_NUMBER /// CellFormatter defined here. The CellFormat.GENERAL_FORMAT CellFormat @@ -70,8 +70,8 @@ public class CellNumberFormatter : CellFormatter /// private sealed class GeneralNumberFormatter : CellFormatter { - private GeneralNumberFormatter() - : base("General") + public GeneralNumberFormatter(CultureInfo locale) + : base(locale, "General") { } @@ -89,7 +89,8 @@ public override void FormatValue(StringBuilder toAppendTo, Object value) { double num; double.TryParse(value.ToString(), out num); - cf = (num % 1.0 == 0) ? SIMPLE_INT : SIMPLE_FLOAT; + cf = (num % 1.0 == 0) ? new CellNumberFormatter(locale, "#") : + new CellNumberFormatter(locale, "#.#"); } else { @@ -104,40 +105,6 @@ public override void SimpleValue(StringBuilder toAppendTo, Object value) } } - private sealed class SimpleNumberCellFormatter : CellFormatter - { - public SimpleNumberCellFormatter(string format) - : base(format) - { - - } - public override void FormatValue(StringBuilder toAppendTo, Object value) - { - if (value == null) - return; - //if (value is Number) { - if (NPOI.Util.Number.IsNumber(value)) - { - double num; - double.TryParse(value.ToString(), out num); - if (num % 1.0 == 0) - SIMPLE_INT.FormatValue(toAppendTo, value); - else - SIMPLE_FLOAT.FormatValue(toAppendTo, value); - } - else - { - CellTextFormatter.SIMPLE_TEXT.FormatValue(toAppendTo, value); - } - } - public override void SimpleValue(StringBuilder toAppendTo, Object value) - { - FormatValue(toAppendTo, value); - } - } - - - /** * This class is used to mark where the special characters in the format * are, as opposed to the other characters that are simply printed. @@ -166,8 +133,19 @@ public override String ToString() * @param format The format to Parse. */ public CellNumberFormatter(String format) - : base(format) + : this(LocaleUtil.GetUserLocale(), format) + { + } + /** + * Creates a new cell number formatter. + * + * @param locale The locale to use. + * @param format The format to parse. + */ + public CellNumberFormatter(CultureInfo locale, String format) + : base(locale, format) { + this.SIMPLE_NUMBER = new GeneralNumberFormatter(locale); CellNumberPartHandler ph = new CellNumberPartHandler(); StringBuilder descBuf = CellFormatPart.ParseFormat(format, CellFormatType.NUMBER, ph); @@ -240,7 +218,7 @@ public CellNumberFormatter(String format) } double[] scaleByRef = { ph.Scale }; - integerCommas = interpretIntegerCommas(descBuf, specials, decimalPoint, integerEnd(), fractionalEnd(), scaleByRef); + showGroupingSeparator = interpretIntegerCommas(descBuf, specials, decimalPoint, integerEnd(), fractionalEnd(), scaleByRef); if (exponent == null) { scale = scaleByRef[0]; @@ -348,7 +326,7 @@ public CellNumberFormatter(String format) } fmtBuf.Append('E'); placeZeros(fmtBuf, exponentSpecials.GetRange(2, exponentSpecials.Count - 2)); - decimalFmt = new DecimalFormat(fmtBuf.ToString()); + decimalFmt = new DecimalFormat(fmtBuf.ToString(), locale.NumberFormat); printfFmt = null; } @@ -356,6 +334,7 @@ public CellNumberFormatter(String format) desc = descBuf.ToString(); } + private static void placeZeros(StringBuilder sb, List specials) { foreach (Special s in specials) @@ -585,7 +564,7 @@ public override void FormatValue(StringBuilder toAppendTo, Object valueObject) } SortedList mods = new SortedList(); - StringBuilder output = new StringBuilder(desc); + StringBuilder output = new StringBuilder(localiseFormat(desc)); if (exponent != null) { @@ -598,13 +577,12 @@ public override void FormatValue(StringBuilder toAppendTo, Object valueObject) else { StringBuilder result = new StringBuilder(); - //Formatter f = new Formatter(result); - //f.Format(LOCALE, printfFmt, value); - result.Append(value.ToString(printfFmt)); + + result.Append(value.ToString(printfFmt, locale)); if (numerator == null) { WriteFractional(result, output); - Writeint(result, output, integerSpecials, mods, integerCommas); + Writeint(result, output, integerSpecials, mods, showGroupingSeparator); } else { @@ -612,6 +590,8 @@ public override void FormatValue(StringBuilder toAppendTo, Object valueObject) } } + String groupingSeparator = locale.NumberFormat.NumberGroupSeparator; + // Now strip out any remaining '#'s and add any pending text ... IEnumerator it = specials.GetEnumerator();//.ListIterator(); IEnumerator Changes = mods.Keys.GetEnumerator(); @@ -636,7 +616,7 @@ public override void FormatValue(StringBuilder toAppendTo, Object valueObject) { case CellNumberStringMod.AFTER: // ignore Adding a comma After a deleted char (which was a '#') - if (nextChange.ToAdd.Equals(",") && deletedChars.Get(s.pos)) + if (nextChange.ToAdd.Equals(groupingSeparator) && deletedChars.Get(s.pos)) break; output.Insert(modPos + 1, nextChange.ToAdd); break; @@ -734,7 +714,7 @@ private void WriteScientific(double value, StringBuilder output, SortedList[] numSpecials) { foreach (List specials in numSpecials) @@ -923,18 +950,20 @@ private void WriteSingleint(String fmt, int num, StringBuilder output, { StringBuilder sb = new StringBuilder(); - //Formatter formatter = new Formatter(sb); - //formatter.Format(LOCALE, fmt, num); - sb.Append(num.ToString(fmt)); + + + sb.Append(num.ToString(fmt, locale)); Writeint(sb, output, numSpecials, mods, false); } private void Writeint(StringBuilder result, StringBuilder output, List numSpecials, SortedList mods, - bool ShowCommas) + bool showGroupingSeparator) { - - int pos = result.ToString().IndexOf('.') - 1; + NumberFormatInfo dfs = locale.NumberFormat; + String decimalSeparator = dfs.NumberDecimalSeparator; + String groupingSeparator = dfs.NumberGroupSeparator; + int pos = result.ToString().IndexOf(decimalSeparator) - 1; if (pos < 0) { if (exponent != null && numSpecials == integerSpecials) @@ -947,12 +976,12 @@ private void Writeint(StringBuilder result, StringBuilder output, for (strip = 0; strip < pos; strip++) { char resultCh = result[strip]; - if (resultCh != '0' && resultCh != ',') + if (resultCh != '0' && resultCh != groupingSeparator[0]) break; } //ListIterator it = numSpecials.ListIterator(numSpecials.Count); - bool followWithComma = false; + bool followWithGroupingSeparator = false; Special lastOutputintDigit = null; int digit = 0; //while (it.HasPrevious()) { @@ -967,7 +996,7 @@ private void Writeint(StringBuilder result, StringBuilder output, resultCh = '0'; } Special s = numSpecials[i]; - followWithComma = ShowCommas && digit > 0 && digit % 3 == 0; + followWithGroupingSeparator = showGroupingSeparator && digit > 0 && digit % 3 == 0; bool zeroStrip = false; if (resultCh != '0' || s.ch == '0' || s.ch == '?' || pos >= strip) { @@ -975,10 +1004,10 @@ private void Writeint(StringBuilder result, StringBuilder output, output[s.pos] = (zeroStrip ? ' ' : resultCh); lastOutputintDigit = s; } - if (followWithComma) + if (followWithGroupingSeparator) { - mods.Add(insertMod(s, zeroStrip ? " " : ",", CellNumberStringMod.AFTER), null); - followWithComma = false; + mods.Add(insertMod(s, zeroStrip ? " " : groupingSeparator, CellNumberStringMod.AFTER), null); + followWithGroupingSeparator = false; } digit++; --pos; @@ -990,12 +1019,12 @@ private void Writeint(StringBuilder result, StringBuilder output, // pos was decremented at the end of the loop above when the iterator was at its end ++pos; extraLeadingDigits = new StringBuilder(result.ToString().Substring(0, pos)); - if (ShowCommas) + if (showGroupingSeparator) { while (pos > 0) { if (digit > 0 && digit % 3 == 0) - extraLeadingDigits.Insert(pos, ','); + extraLeadingDigits.Insert(pos, groupingSeparator); digit++; --pos; } @@ -1011,7 +1040,8 @@ private void WriteFractional(StringBuilder result, StringBuilder output) if (fractionalSpecials.Count > 0) { string resultString = result.ToString(); - digit = resultString.IndexOf('.') + 1; + String decimalSeparator = locale.NumberFormat.NumberDecimalSeparator; + digit = resultString.IndexOf(decimalSeparator) + 1; if (exponent != null) strip = resultString.IndexOf('E') - 1; else diff --git a/main/SS/Formula/Functions/Mirr.cs b/main/SS/Formula/Functions/Mirr.cs index 823951e94..41a2797be 100644 --- a/main/SS/Formula/Functions/Mirr.cs +++ b/main/SS/Formula/Functions/Mirr.cs @@ -52,7 +52,7 @@ public Mirr() } - protected override int MaxNumOperands + internal override int MaxNumOperands { get { diff --git a/main/SS/Formula/Functions/MultiOperandNumericFunction.cs b/main/SS/Formula/Functions/MultiOperandNumericFunction.cs index fc7345de7..8a28ef36c 100644 --- a/main/SS/Formula/Functions/MultiOperandNumericFunction.cs +++ b/main/SS/Formula/Functions/MultiOperandNumericFunction.cs @@ -115,7 +115,7 @@ public void Add(double value) * Maximum number of operands accepted by this function. * Subclasses may override to Change default value. */ - protected virtual int MaxNumOperands + internal virtual int MaxNumOperands { get { diff --git a/main/SS/Formula/PTG/AbstractFunctionPtg.cs b/main/SS/Formula/PTG/AbstractFunctionPtg.cs index 9b3a9a8c2..8d377d0ff 100644 --- a/main/SS/Formula/PTG/AbstractFunctionPtg.cs +++ b/main/SS/Formula/PTG/AbstractFunctionPtg.cs @@ -21,6 +21,7 @@ namespace NPOI.SS.Formula.PTG using System.Text; using NPOI.SS.Formula.Function; + using NPOI.Util; /** @@ -44,13 +45,17 @@ public abstract class AbstractFunctionPtg : OperationPtg protected byte returnClass; protected byte[] paramClass; - protected byte _numberOfArgs; + protected int _numberOfArgs; protected short _functionIndex; protected AbstractFunctionPtg(int functionIndex, int pReturnClass, byte[] paramTypes, int nParams) { - _numberOfArgs = (byte)nParams; + _numberOfArgs = nParams; + if (functionIndex < short.MinValue || functionIndex > short.MaxValue) + throw new RuntimeException("functionIndex " + functionIndex + " cannot be cast to short"); _functionIndex = (short)functionIndex; + if (pReturnClass < Byte.MinValue || pReturnClass > Byte.MaxValue) + throw new RuntimeException("pReturnClass " + pReturnClass + " cannot be cast to byte"); returnClass = (byte)pReturnClass; paramClass = paramTypes; } diff --git a/main/SS/UserModel/DataFormatter.cs b/main/SS/UserModel/DataFormatter.cs index 8b026fbb2..08a979e81 100644 --- a/main/SS/UserModel/DataFormatter.cs +++ b/main/SS/UserModel/DataFormatter.cs @@ -15,19 +15,18 @@ the License. You may obtain a copy of the License at limitations under the License. ==================================================================== */ +using Cysharp.Text; +using ExtendedNumerics; +using NPOI.SS.Format; +using NPOI.SS.Formula; +using NPOI.SS.Util; +using NPOI.Util; using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Text.RegularExpressions; -using NPOI.SS.Util; -using NPOI.SS.Format; -using NPOI.SS.Formula; -using NPOI.Util; - -using Cysharp.Text; - namespace NPOI.SS.UserModel { /** @@ -178,7 +177,7 @@ static DataFormatter() /** A default FormatBase to use when a number pattern cannot be Parsed. */ private FormatBase defaultNumFormat; - private CultureInfo currentCulture; + private CultureInfo locale; /* * A map to cache formats. @@ -247,7 +246,7 @@ public DataFormatter(CultureInfo locale, bool emulateCSV) public DataFormatter(CultureInfo culture, bool localeIsAdapting, bool emulateCSV) { this.localeIsAdapting = true; - this.currentCulture = culture; + this.locale = culture; //localeChangedObservable.addObserver(this); // localeIsAdapting must be true prior to this first checkForLocaleChange call. //localeChangedObservable.checkForLocaleChange(culture); @@ -297,12 +296,6 @@ public DataFormatter(CultureInfo culture, bool localeIsAdapting, bool emulateCSV * @param cell The cell to retrieve a FormatBase for * @return A FormatBase for the FormatBase String */ - private FormatBase GetFormat(ICell cell) - { - - return GetFormat(cell, null); - } - private FormatBase GetFormat(ICell cell, ConditionalFormattingEvaluator cfEvaluator) { if (cell == null) return null; @@ -346,7 +339,7 @@ private FormatBase GetFormat(double cellValue, int formatIndex, String formatStr try { // Ask CellFormat to get a formatter for it - CellFormat cfmt = CellFormat.GetInstance(formatStr); + CellFormat cfmt = CellFormat.GetInstance(locale, formatStr); // CellFormat requires callers to identify date vs not, so do so object cellValueO = (cellValue); if (DateUtil.IsADateFormat(formatIndex, formatStr) && @@ -714,7 +707,7 @@ Excel displays the month instead of minutes." try { //return new SimpleDateFormat(formatStr); - return new ExcelStyleDateFormatter(formatStr); + return new ExcelStyleDateFormatter(formatStr, dateSymbols); } catch (ArgumentException) { @@ -822,6 +815,81 @@ private string cleanFormatForNumber(string formatStr) return sb.ToString(); } + + private class InternalDecimalFormatWithScale : FormatBase + { + + private static Regex endsWithCommas = new Regex("(,+)$", RegexOptions.Compiled); + private BigDecimal? divider; + private static BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private DecimalFormat df; + private static string TrimTrailingCommas(string s) + { + return Regex.Replace(s, ",+$", ""); + } + + public InternalDecimalFormatWithScale(string pattern, NumberFormatInfo symbols) + { + df = new DecimalFormat(TrimTrailingCommas(pattern), symbols); + //SetExcelStyleRoundingMode(df); + Match endsWithCommasMatcher = endsWithCommas.Match(pattern); + if(endsWithCommasMatcher.Success) + { + string commas = endsWithCommasMatcher.Groups[1].Value; + BigDecimal temp = BigDecimal.One; + for(int i = 0; i < commas.Length; ++i) + { + temp = BigDecimal.Multiply(temp, ONE_THOUSAND); + } + divider = temp; + } + else + { + divider = null; + } + } + + private object ScaleInput(object obj) + { + if(divider != null) + { + if(obj is BigDecimal) + { + obj = BigDecimal.Divide((BigDecimal) obj, divider.Value); + } + else if(obj is double) + { + obj = (double) obj / ((double?) divider); + } + else if(obj is Int32 || obj is Int64 || obj is decimal) + { + obj = (BigDecimal) obj / divider; + } + else + { + throw new InvalidOperationException(); + } + } + return obj; + } + + public override string Format(object obj) + { + obj = ScaleInput(obj); + return df.Format(obj, CultureInfo.CurrentCulture); + } + + public override StringBuilder Format(object obj, StringBuilder toAppendTo, CultureInfo culture) + { + obj = ScaleInput(obj); + return df.Format(obj, toAppendTo, culture); + } + public override object ParseObject(string source, int pos) + { + throw new InvalidOperationException(); + } + } + private FormatBase CreateNumberFormat(string formatStr, double cellValue) { string format = cleanFormatForNumber(formatStr); @@ -838,7 +906,7 @@ private FormatBase CreateNumberFormat(string formatStr, double cellValue) // correct grouping for non-US locales. if (grouping != ',') { - symbols = currentCulture.NumberFormat.Clone() as NumberFormatInfo; + symbols = locale.NumberFormat.Clone() as NumberFormatInfo; symbols.NumberGroupSeparator = grouping.ToString(); string oldPart = agm.Groups[1].Value; string newPart = oldPart.Replace(grouping, ','); @@ -850,7 +918,7 @@ private FormatBase CreateNumberFormat(string formatStr, double cellValue) { //DecimalFormat df = new DecimalFormat(format, symbols); //setExcelStyleRoundingMode(df); - return new DecimalFormat(format, symbols); + return new InternalDecimalFormatWithScale(format, symbols); } catch (ArgumentException) { @@ -930,7 +998,7 @@ private String GetFormattedNumberString(ICell cell, ConditionalFormattingEvaluat double d = cell.NumericCellValue; if (numberFormat == null) { - return d.ToString(currentCulture); + return d.ToString(locale); } //return numberFormat.Format(d, currentCulture); string formatted = numberFormat.Format(d); @@ -997,7 +1065,7 @@ public string FormatRawCellContents(double value, int formatIndex, string format FormatBase numberFormat = GetFormat(value, formatIndex, formatString); if (numberFormat == null) { - return value.ToString(currentCulture); + return value.ToString(locale); } // When formatting 'value', double to text to BigDecimal produces more // accurate results than double to Double in JDK8 (as compared to @@ -1012,7 +1080,7 @@ public string FormatRawCellContents(double value, int formatIndex, string format } else { - result = numberFormat.Format(decimal.Parse(textValue)); + result = numberFormat.Format(BigDecimal.Parse(textValue)); } // Complete scientific notation by adding the missing +. if (result.Contains('E') && !result.Contains("E-")) @@ -1206,13 +1274,13 @@ public void AddFormat(string excelformatStr, FormatBase format) public void Update(IObservable observable, object localeObj) { if (localeObj is not CultureInfo newLocale) return; - if (newLocale.Equals(currentCulture)) return; + if (newLocale.Equals(locale)) return; - currentCulture = newLocale; + locale = newLocale; //dateSymbols = DateFormatSymbols.getInstance(currentCulture); //decimalSymbols = DecimalFormatSymbols.getInstance(currentCulture); - generalNumberFormat = new ExcelGeneralNumberFormat(currentCulture); + generalNumberFormat = new ExcelGeneralNumberFormat(locale); // init built-in formats diff --git a/main/SS/Util/Format.cs b/main/SS/Util/Format.cs index bb34ec289..0d3ffd683 100644 --- a/main/SS/Util/Format.cs +++ b/main/SS/Util/Format.cs @@ -2,6 +2,7 @@ using System.Text; using System.Globalization; using System.Text.RegularExpressions; +using ExtendedNumerics; namespace NPOI.SS.Util { @@ -188,7 +189,7 @@ public DecimalFormat() private string _pattern; private readonly NumberFormatInfo _formatInfo; - + public DecimalFormat(string pattern) { if (pattern.Contains('\'')) @@ -206,7 +207,7 @@ public string Pattern } private static readonly Regex RegexFraction = new Regex("#+/#+", RegexOptions.Compiled); - public override string Format(Object obj) + public override string Format(object obj) { return Format(obj, CultureInfo.CurrentCulture); } @@ -226,7 +227,10 @@ public override string Format(object obj, CultureInfo culture) } else { - var value = Convert.ToDouble(obj, CultureInfo.InvariantCulture); + object toConvert = obj; + if(obj is BigDecimal) + toConvert = obj.ToString(); + var value = Convert.ToDouble(toConvert, CultureInfo.InvariantCulture); var ret = value.ToString(_pattern, culture); if (string.IsNullOrEmpty(ret)) ret = "0"; diff --git a/main/Util/Number.cs b/main/Util/Number.cs index 191502b81..41edacc8d 100644 --- a/main/Util/Number.cs +++ b/main/Util/Number.cs @@ -1,4 +1,5 @@ -using System; +using ExtendedNumerics; +using System; using System.Collections; namespace NPOI.Util @@ -27,6 +28,7 @@ public static int BitCount(int i) private static Type IntPtrType = typeof(IntPtr); private static Type UIntPtrType = typeof(UIntPtr); private static Type DecimalType = typeof(decimal); + private static Type BigDecimalType = typeof(BigDecimal); public static bool IsNumber(object value) { if (value == null) @@ -35,7 +37,8 @@ public static bool IsNumber(object value) } Type objType = value.GetType(); - if (objType.IsPrimitive || objType == DecimalType) + if (objType.IsPrimitive || objType == DecimalType + || objType == BigDecimalType) { return (objType != BoolType && objType != CharType && diff --git a/ooxml/XSSF/Streaming/SheetDataWriter.cs b/ooxml/XSSF/Streaming/SheetDataWriter.cs index fd99ce94e..9bbac6b89 100644 --- a/ooxml/XSSF/Streaming/SheetDataWriter.cs +++ b/ooxml/XSSF/Streaming/SheetDataWriter.cs @@ -14,6 +14,7 @@ the License. You may obtain a copy of the License at See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ + using System; using System.Globalization; using System.IO; @@ -26,7 +27,7 @@ limitations under the License. namespace NPOI.XSSF.Streaming { - public class SheetDataWriter + public class SheetDataWriter : ICloseable { private static readonly POILogger logger = POILogFactory.GetLogger(typeof(SheetDataWriter)); @@ -261,7 +262,7 @@ private void EndRow() { WriteAsBytes("\n"); } - + //TODO: Fix test case TestCases.XSSF.UserModel.TestEncodingBelowAscii public void WriteCell(int columnIndex, ICell cell) { if (cell == null) @@ -421,7 +422,7 @@ private static bool HasLeadingTrailingSpaces(string str) } //Taken from jdk1.3/src/javax/swing/text/html/HTMLWriter.java - protected void OutputQuotedString(string s) + protected internal void OutputQuotedString(string s) { if (string.IsNullOrEmpty(s)) { @@ -505,7 +506,7 @@ protected void OutputQuotedString(string s) default: // YK: XmlBeans silently replaces all ISO control characters ( < 32) with question marks. // the same rule applies to unicode surrogates and "not a character" symbols. - if (c < ' ' || Char.IsLowSurrogate(c) || Char.IsHighSurrogate(c) || '\uFFFE' <= c) + if (ReplaceWithQuestionMark(c)) { if (counter > last) { @@ -514,6 +515,16 @@ protected void OutputQuotedString(string s) WriteAsBytes("?"); last = counter + 1; } + else if (Char.IsLowSurrogate(c) || Char.IsHighSurrogate(c)) + { + if (counter > last) + { + WriteAsBytes(GetSubArray(chars, last, counter - last)); + } + //WriteAsBytes(c); + _outputWriter.Write(c); + last = counter + 1; + } else if (c > 127) { if (counter > last) @@ -540,7 +551,10 @@ private static ArraySegment GetSubArray(char[] oldArray, int skip, int tak { return new ArraySegment(oldArray, skip, take); } - + public static bool ReplaceWithQuestionMark(char c) + { + return c < ' ' || ('\uFFFE' <= c && c <= '\uFFFF'); + } /** * Deletes the temporary file that backed this sheet on disk. * @return true if the file was deleted, false if it wasn't. diff --git a/testcases/main/SS/Formula/Functions/TestMultiOperandNumericFunction.cs b/testcases/main/SS/Formula/Functions/TestMultiOperandNumericFunction.cs new file mode 100644 index 000000000..75d9b904a --- /dev/null +++ b/testcases/main/SS/Formula/Functions/TestMultiOperandNumericFunction.cs @@ -0,0 +1,49 @@ +/* ==================================================================== + 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. +==================================================================== */ + +namespace TestCases.SS.Formula.Functions +{ + using NPOI.SS; + using NPOI.SS.Formula.Functions; + using NUnit.Framework; + using NUnit.Framework.Legacy; + + [TestFixture] + public class TestMultiOperandNumericFunction + { + + [Test] + public void TestSettings() + { + MultiOperandNumericFunction fun = new MultiOperandNumericFunction1(true, true); + ClassicAssert.AreEqual(SpreadsheetVersion.EXCEL2007.MaxFunctionArgs, fun.MaxNumOperands); + } + } + + public class MultiOperandNumericFunction1 : MultiOperandNumericFunction + { + public MultiOperandNumericFunction1(bool isReferenceBoolCounted, bool isBlankCounted) + : base(isReferenceBoolCounted, isBlankCounted) + { + + } + protected internal override double Evaluate(double[] values) + { + return 0; + } + } +} diff --git a/testcases/main/SS/Formula/PTG/TestAbstractFunctionPtg.cs b/testcases/main/SS/Formula/PTG/TestAbstractFunctionPtg.cs new file mode 100644 index 000000000..ce37d69a4 --- /dev/null +++ b/testcases/main/SS/Formula/PTG/TestAbstractFunctionPtg.cs @@ -0,0 +1,86 @@ +/* ==================================================================== + 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. +==================================================================== */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace TestCases.SS.Formula.PTG +{ + using NPOI.SS.Formula.PTG; + using NPOI.Util; + using NUnit.Framework; + using NUnit.Framework.Legacy; + [TestFixture] + public class TestAbstractFunctionPtg + { + [Test] + public void TestConstructor() + { + FunctionPtg ptg = new FunctionPtg(1, 2, null, 255); + ClassicAssert.AreEqual(1, ptg.FunctionIndex); + ClassicAssert.AreEqual(2, ptg.DefaultOperandClass); + ClassicAssert.AreEqual(255, ptg.NumberOfOperands); + } + + [Test] + public void TestInvalidFunctionIndex() + { + ClassicAssert.Throws(()=>{ + new FunctionPtg(40000, 2, null, 255); + }); + + } + + [Test] + public void TestInvalidRuntimeClass() + { + ClassicAssert.Throws(()=>{ + new FunctionPtg(1, 300, null, 255); + }); + } + + private class FunctionPtg : AbstractFunctionPtg + { + + internal FunctionPtg(int functionIndex, int pReturnClass, + byte[] paramTypes, int nParams) + : base(functionIndex, pReturnClass, paramTypes, nParams) + { + + } + + public override int Size + { + get + { + return 0; + } + } + + public override void Write(ILittleEndianOutput out1) + { + + } + } + } +} + + diff --git a/testcases/main/SS/UserModel/TestDataFormatter.cs b/testcases/main/SS/UserModel/TestDataFormatter.cs index e7b81977b..0862ab6d7 100644 --- a/testcases/main/SS/UserModel/TestDataFormatter.cs +++ b/testcases/main/SS/UserModel/TestDataFormatter.cs @@ -763,7 +763,7 @@ public void TestIsADateFormat() } [Test] - public void testLargeNumbersAndENotation() + public void TestLargeNumbersAndENotation() { assertFormatsTo("1E+86", 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999d); assertFormatsTo("1E-84", 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000001d); @@ -833,6 +833,36 @@ public void TestFormulaEvaluation() wb.Close(); } + [Test] + public void TestFormatWithTrailingDotsUS() { + DataFormatter dfUS = new DataFormatter(CultureInfo.GetCultureInfo("en-US")); + ClassicAssert.AreEqual("1,000,000", dfUS.FormatRawCellContents(1000000, -1, "#,##0")); + ClassicAssert.AreEqual("1,000", dfUS.FormatRawCellContents(1000000, -1, "#,##0,")); + ClassicAssert.AreEqual("1", dfUS.FormatRawCellContents(1000000, -1, "#,##0,,")); + ClassicAssert.AreEqual("1,000,000.0", dfUS.FormatRawCellContents(1000000, -1, "#,##0.0")); + ClassicAssert.AreEqual("1,000.0", dfUS.FormatRawCellContents(1000000, -1, "#,##0.0,")); + ClassicAssert.AreEqual("1.0", dfUS.FormatRawCellContents(1000000, -1, "#,##0.0,,")); + ClassicAssert.AreEqual("1,000,000.00", dfUS.FormatRawCellContents(1000000, -1, "#,##0.00")); + ClassicAssert.AreEqual("1,000.00", dfUS.FormatRawCellContents(1000000, -1, "#,##0.00,")); + ClassicAssert.AreEqual("1.00", dfUS.FormatRawCellContents(1000000, -1, "#,##0.00,,")); + ClassicAssert.AreEqual("1,000,000", dfUS.FormatRawCellContents(1e24, -1, "#,##0,,,,,,")); + } + + [Test] + public void TestFormatWithTrailingDotsOtherLocale() { + DataFormatter dfIT = new DataFormatter(CultureInfo.GetCultureInfo("it-IT")); + ClassicAssert.AreEqual("1.000.000", dfIT.FormatRawCellContents(1000000, -1, "#,##0")); + ClassicAssert.AreEqual("1.000", dfIT.FormatRawCellContents(1000000, -1, "#,##0,")); + ClassicAssert.AreEqual("1", dfIT.FormatRawCellContents(1000000, -1, "#,##0,,")); + ClassicAssert.AreEqual("1.000.000,0", dfIT.FormatRawCellContents(1000000, -1, "#,##0.0")); + ClassicAssert.AreEqual("1.000,0", dfIT.FormatRawCellContents(1000000, -1, "#,##0.0,")); + ClassicAssert.AreEqual("1,0", dfIT.FormatRawCellContents(1000000, -1, "#,##0.0,,")); + ClassicAssert.AreEqual("1.000.000,00", dfIT.FormatRawCellContents(1000000, -1, "#,##0.00")); + ClassicAssert.AreEqual("1.000,00", dfIT.FormatRawCellContents(1000000, -1, "#,##0.00,")); + ClassicAssert.AreEqual("1,00", dfIT.FormatRawCellContents(1000000, -1, "#,##0.00,,")); + ClassicAssert.AreEqual("1.000.000", dfIT.FormatRawCellContents(1e24, -1, "#,##0,,,,,,")); + } + /** * bug 60031: DataFormatter parses months incorrectly when put at the end of date segment */ @@ -868,18 +898,15 @@ Excel displays the month instead of minutes." public void TestBug60422() { //LocaleUtil.setUserLocale(Locale.ROOT); - try - { - char euro = '\u20AC'; - DataFormatter df = new DataFormatter(CultureInfo.GetCultureInfo("de-DE")); - String formatString = String.Format("_-* #,##0.00\\ \"{0}\"_-;\\-* #,##0.00\\ \"{1}\"_-;_-* \"-\"??\\ \"{2}\"_-;_-@_-", - euro, euro, euro); - ClassicAssert.AreEqual("4.33 " + euro, df.FormatRawCellContents(4.33, 178, formatString)); - } - finally - { - //LocaleUtil.resetUserLocale(); - } + + char euro = '\u20AC'; + DataFormatter df = new DataFormatter(CultureInfo.GetCultureInfo("de-DE")); + String formatString = String.Format("_-* #,##0.00\\ \"{0}\"_-;\\-* #,##0.00\\ \"{1}\"_-;_-* \"-\"??\\ \"{2}\"_-;_-@_-", + euro, euro, euro); + + ClassicAssert.AreEqual("4,33 " + euro, df.FormatRawCellContents(4.33, 178, formatString)); + ClassicAssert.AreEqual("1.234,33 " + euro, df.FormatRawCellContents(1234.33, 178, formatString)); + } } } \ No newline at end of file diff --git a/testcases/ooxml/XSSF/Streaming/TestSheetDataWriter.cs b/testcases/ooxml/XSSF/Streaming/TestSheetDataWriter.cs new file mode 100644 index 000000000..a3d3ca0c4 --- /dev/null +++ b/testcases/ooxml/XSSF/Streaming/TestSheetDataWriter.cs @@ -0,0 +1,121 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace TestCases.XSSF.Streaming +{ + using NPOI.Util; + using NPOI.XSSF.Streaming; + using NUnit.Framework;using NUnit.Framework.Legacy; + + [TestFixture] + public sealed class TestSheetDataWriter + { + + String unicodeSurrogates = "\uD835\uDF4A\uD835\uDF4B\uD835\uDF4C\uD835\uDF4D\uD835\uDF4E" + + "\uD835\uDF4F\uD835\uDF50\uD835\uDF51\uD835\uDF52\uD835\uDF53\uD835\uDF54\uD835" + + "\uDF55\uD835\uDF56\uD835\uDF57\uD835\uDF58\uD835\uDF59\uD835\uDF5A\uD835\uDF5B" + + "\uD835\uDF5C\uD835\uDF5D\uD835\uDF5E\uD835\uDF5F\uD835\uDF60\uD835\uDF61\uD835" + + "\uDF62\uD835\uDF63\uD835\uDF64\uD835\uDF65\uD835\uDF66\uD835\uDF67\uD835\uDF68" + + "\uD835\uDF69\uD835\uDF6A\uD835\uDF6B\uD835\uDF6C\uD835\uDF6D\uD835\uDF6E\uD835" + + "\uDF6F\uD835\uDF70\uD835\uDF71\uD835\uDF72\uD835\uDF73\uD835\uDF74\uD835\uDF75" + + "\uD835\uDF76\uD835\uDF77\uD835\uDF78\uD835\uDF79\uD835\uDF7A"; + + [Test] + public void TestReplaceWithQuestionMark() + { + for (int i = 0; i < unicodeSurrogates.Length; i++) + { + ClassicAssert.IsFalse(SheetDataWriter.ReplaceWithQuestionMark(unicodeSurrogates[i])); + } + ClassicAssert.IsTrue(SheetDataWriter.ReplaceWithQuestionMark('\uFFFE')); + ClassicAssert.IsTrue(SheetDataWriter.ReplaceWithQuestionMark('\uFFFF')); + ClassicAssert.IsTrue(SheetDataWriter.ReplaceWithQuestionMark('\u0000')); + ClassicAssert.IsTrue(SheetDataWriter.ReplaceWithQuestionMark('\u000F')); + ClassicAssert.IsTrue(SheetDataWriter.ReplaceWithQuestionMark('\u001F')); + } + + [Test] + public void TestWriteUnicodeSurrogates() + { + SheetDataWriter writer = new SheetDataWriter(); + try + { + writer.OutputQuotedString(unicodeSurrogates); + writer.Close(); + FileInfo file = writer.TempFileInfo; + FileInputStream is1 = new FileInputStream(file.Open(FileMode.OpenOrCreate)); + String text; + try + { + byte[] data = IOUtils.ToByteArray(is1); + int index = 0; + if (data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) + index = 3; + text = Encoding.UTF8.GetString(data, index, data.Length - index); + } + finally + { + is1.Close(); + } + ClassicAssert.AreEqual(unicodeSurrogates, text); + } + finally + { + IOUtils.CloseQuietly(writer); + } + } + + [Test] + public void TestWriteNewLines() + { + SheetDataWriter writer = new SheetDataWriter(); + try + { + writer.OutputQuotedString("\r\n"); + writer.Close(); + FileInfo file = writer.TempFileInfo; + FileInputStream is1 = new FileInputStream(file.Open(FileMode.OpenOrCreate)); + String text; + try + { + byte[] data = IOUtils.ToByteArray(is1); + int index = 0; + if (data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) + index = 3; + text = Encoding.UTF8.GetString(data, index, data.Length - index); + } + finally + { + is1.Close(); + } + ClassicAssert.AreEqual(" ", text); + } + finally + { + IOUtils.CloseQuietly(writer); + } + } + } +} diff --git a/testcases/ooxml/XSSF/UserModel/TestCloneSheet.cs b/testcases/ooxml/XSSF/UserModel/TestCloneSheet.cs index 645bc2b69..1d2c2b481 100644 --- a/testcases/ooxml/XSSF/UserModel/TestCloneSheet.cs +++ b/testcases/ooxml/XSSF/UserModel/TestCloneSheet.cs @@ -1,7 +1,9 @@ -using NUnit.Framework;using NUnit.Framework.Legacy; +using NPOI.XSSF; +using NUnit.Framework; +using NUnit.Framework.Legacy; using System.IO; -namespace NPOI.XSSF.UserModel +namespace TestCases.XSSF.UserModel { [TestFixture] public class TestCloneSheet diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFCell.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFCell.cs index 8dfea9539..0f5082bbb 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFCell.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFCell.cs @@ -501,6 +501,7 @@ public void TestBug56644CreateBlank() } } [Test] + [Ignore("SheetDataWriter.OutputQuotedString throws EncoderFallbackException:Unable to translate Unicode character \\uD800")] public void TestEncodingBelowAscii() { StringBuilder sb = new StringBuilder();