OOXML中回車等特殊字元處理方法
阿新 • • 發佈:2019-12-06
問題點:NPOI處理xlsx文件時,將\r寫成了換行符。
例項:以下字元abc\rcde
如果直接複製到Excel 2016,顯示結果如下(單元格設定為折行顯示):
如果用NPOI寫入Xlsx文件,顯示結果如下(單元格設定為折行顯示):
程式碼如下:
string path = @"C: \Users\Desktop\test.xlsx"; var book = new XSSFWorkbook(); var sheet = book.CreateSheet("test"); var row = sheet.GetRow(1) ?? sheet.CreateRow(1); var cell = row.GetCell(1) ?? row.CreateCell(1); cell.SetCellValue("abc\rcde"); using (var file = new FileStream(path, FileMode.Create, FileAccess.Write)) { book.Write(file); file.Close(); }
檢視生成的Excel內部資料確實成了換行符:
原因
OOXML因為使用XML格式儲存資料,所以XML中無法表示的字元需要轉換為Unicode碼儲存,Excel開啟時會自動將這些Unicode碼轉換為原來的字元顯示。由於NPOI需要相容以前版本Excel,而沒有處理'\t' '\n' '\r'這幾個字元。
NPOI原始碼:
public static string ExcelEncodeString(string t) { StringWriter sw = new StringWriter(); //poi dose not add prefix _x005f before _x????_ char. //if (Regex.IsMatch(t, "(_x[0-9A-F]{4,4}_)")) //{ // Match match = Regex.Match(t, "(_x[0-9A-F]{4,4}_)"); // int indexAdd = 0; // while (match.Success) // { // t = t.Insert(match.Index + indexAdd, "_x005F"); // indexAdd += 6; // match = match.NextMatch(); // } //} for (int i = 0; i < t.Length; i++) { if (t[i] <= 0x1f && t[i] != '\t' && t[i] != '\n' && t[i] != '\r') //Not Tab, CR or LF { //[0x00-0x0a]-[\r\n\t] //poi replace those chars with ? sw.Write('?'); //sw.Write("_x00{0}_", (t[i] < 0xa ? "0" : "") + ((int)t[i]).ToString("X")); } else if (t[i] == '\uFFFE') { sw.Write('?'); } else { sw.Write(t[i]); } } return sw.ToString(); }
對應方法
Unicode表裡面需要處理的部分:
遍歷所有字元,將001f內的字元都轉換為Unicode。
字元轉換為Unicode程式碼:
private static string EncodeXmlUTF(string value) { var builder = new StringBuilder(); foreach (char c in value.ToCharArray()) { if (c < 32) { builder.Append($"_x{(c < 16 ? "000" : "00")}{Convert.ToInt32(c):X}_"); } else { builder.Append(c); } } return builder.ToString(); }
NPOI的場合
讀取端:由於NPOI已經做了轉換處理,所有不需要特別的程式碼。
寫入端:
cell.SetCellValue(EncodeXmlUTF(text));
設定多文字的特殊處理:因為NPOI裡面需要用到字串位置資訊,所有在它處理之後替換原先字元為Unicode。
var text = new XSSFRichTextString("abcefg\rhijklmn"); text.ApplyFont(commonFont.Index); text.ApplyFont(1, 10, green_font); foreach (var r in text.GetCTRst().r) { r.t = EncodeXmlUTF(r.t); }
OpenXML的場合
需要在SharedStringTable中寫入SharedStringItem:
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new Text(EncodeXmlUTF(value))));
讀取的時候同理需要將SharedStringItem.InnerText轉碼後的資料轉換回來:
Unicode轉換回來程式碼:
static String UtfDecode(String value) { if (value == null) return null; StringBuilder buf = new StringBuilder(); MatchCollection mc = utfPtrn.Matches(value); int idx = 0; for (int i = 0; i < mc.Count;i++ ) { int pos = mc[i].Index; if (pos > idx) { buf.Append(value.Substring(idx, pos-idx)); } String code = mc[i].Groups[1].Value; int icode = Int32.Parse(code, System.Globalization.NumberStyles.AllowHexSpecifier); buf.Append((char)icode); idx = mc[i].Index+mc[i].Length; } buf.Append(value.Substring(idx)); return buf.ToString(); }
&n