diff --git a/R/wb_load.R b/R/wb_load.R index ee346be9b..a043d03b4 100644 --- a/R/wb_load.R +++ b/R/wb_load.R @@ -1538,6 +1538,36 @@ wb_load <- function( # correct sheet references and replace our replacement with it. if (!data_only && length(workbookBIN)) { + # we need to update the order of customSheetView children. Incorrect orders + # causes spreadsheet software to be unable to load and recover the file. + for (sheet in seq_along(wb$worksheets)) { + if (length(wb$worksheets[[sheet]]$customSheetViews) == 0) next + + cvs <- xml_node(wb$worksheets[[sheet]]$customSheetViews, "customSheetViews", "customSheetView") + + # chart sheets have a reduced custom view + exp_attr <- c( + "guid", "scale", "colorId", "showPageBreaks", "showFormulas", + "showGridLines", "showRowCol", "outlineSymbols", "zeroValues", + "fitToPage", "printArea", "filter", "showAutoFilter", "hiddenRows", + "hiddenColumns", "state", "filterUnique", "view", "showRuler", + "topLeftCell", "zoomToFit" + ) + exp_nams <- c( + "pane", "selection", "rowBreaks", "colBreaks", "pageMargins", + "printOptions", "pageSetup", "headerFooter", "autoFilter", "extLst" + ) + cv <- read_xml2df(read_xml(cvs), "customSheetView", vec_attrs = exp_attr, vec_chlds = exp_nams) + + # headerFooter cause issues. they are (a) not added to the correct node + # and (b) brick the entire XML structure + cv$headerFooter <- "" + + cvs <- write_df2xml(cv[c(exp_attr, exp_nams)], "customSheetView", vec_attrs = exp_attr, vec_chlds = exp_nams) + + wb$worksheets[[sheet]]$customSheetViews <- xml_node_create("customSheetViews", xml_children = cvs) + } + if (length(wb$workbook$xti)) { # create data frame containing sheet names for Xti entries xti <- rbindlist(xml_attr(wb$workbook$xti, "xti")) diff --git a/src/xlsb.cpp b/src/xlsb.cpp index 1d8650aee..17de06455 100644 --- a/src/xlsb.cpp +++ b/src/xlsb.cpp @@ -785,8 +785,64 @@ int32_t table_bin(std::string filePath, std::string outPath, bool debug) { case BrtBeginCustomFilters14: case BrtBeginCustomRichFilters: { - Rcpp::warning("Custom Filter found. This is not handled."); - bin.seekg(size, bin.cur); + int32_t fAnd = 0; + + // in xlsb it is flipped + fAnd = readbin(fAnd, bin, swapit) ^ 1; + + out << ""; + + break; + } + + case BrtCustomFilter: + case BrtCustomFilter14: + { + int8_t vts = 0, grbitSgn = 0; + double union_val = 0; + std::string vtsStringXls; + + vts = readbin(vts, bin, swapit); + grbitSgn = readbin(grbitSgn, bin, swapit); + + if (vts == 4) { + // a double + union_val = Xnum(bin, swapit); + } else if (vts == 8) { + // a bool + int8_t boolean = 0; + boolean = readbin(boolean, bin, swapit); + union_val = static_cast(readbin(boolean, bin, swapit)); + for (int8_t blk = 0; blk < 7; ++blk) { + readbin(boolean, bin, swapit); + } + } else { + // ignore + readbin(union_val, bin, swapit); + readbin(union_val, bin, swapit); + } + + if (vts == 6) // a string + vtsStringXls = XLWideString(bin, swapit); + + out << ""; + + break; + } + + case BrtEndCustomFilters: + case BrtEndCustomRichFilters: + { + + out << "" << std::endl; break; } @@ -1032,10 +1088,12 @@ int32_t comments_bin(std::string filePath, std::string outPath, bool debug) { ref = lref + ":" + rref; } - guid0 = readbin(guid0, bin, swapit); - guid1 = readbin(guid1, bin, swapit); - guid2 = readbin(guid2, bin, swapit); - guid3 = readbin(guid3, bin, swapit); + std::vector guids(4); + guids[0] = readbin(guid0, bin, 0); + guids[1] = readbin(guid1, bin, 0); + guids[2] = readbin(guid2, bin, 0); + guids[3] = readbin(guid3, bin, 0); + out << " defNams, xtis, reference_type; + std::vector defNams, xtis, reference_type, customWorkbookView; defNams.push_back(""); xtis.push_back(""); @@ -1568,6 +1626,82 @@ int32_t workbook_bin(std::string filePath, std::string outPath, bool debug) { break; } + case BrtUserBookView: + { + if (debug) Rcpp::Rcout << "" << std::endl; + + std::ostringstream cwv; + + int32_t xLeft = 0, xRight = 0, yTop = 0, yBot = 0, iTabid = 0, iTabRatio = 0, guid0 = 0, guid1 = 0, guid2 = 0, guid3 = 0, flags = 0; + int16_t wMergeInterval = 0; + std::string stName; + + xLeft = readbin(xLeft, bin, swapit); + xRight = readbin(xRight, bin, swapit); + yTop = readbin(yTop, bin, swapit); + yBot = readbin(yBot, bin, swapit); + iTabid = readbin(iTabid, bin, swapit); + iTabRatio = readbin(iTabRatio, bin, swapit); + + std::vector guids(4); + guids[0] = readbin(guid0, bin, 0); + guids[1] = readbin(guid1, bin, 0); + guids[2] = readbin(guid2, bin, 0); + guids[3] = readbin(guid3, bin, 0); + + wMergeInterval = readbin(wMergeInterval, bin, swapit); + + flags = readbin(flags, bin, swapit); + + BrtUserBookViewFields *fields = (BrtUserBookViewFields*)&flags; + + stName = XLWideString(bin, swapit); + + std::string showComments; + if (fields->mdDspNote == 0) showComments = "commNone"; + if (fields->mdDspNote == 1) showComments = "commIndAndComment"; + if (fields->mdDspNote == 2) showComments = "commIndicator"; + + std::string showObjects; + if (fields->mdHideObj == 0) showObjects = "all"; + if (fields->mdHideObj == 1) showObjects = "placeholders"; + if (fields->mdHideObj == 2) showObjects = "none"; + + cwv << "fTimedUpdate) cwv << " autoUpdate=\"" << fields->fTimedUpdate << "\"" << std::endl; + if (fields->fTimedUpdate) cwv << " mergeInterval=\"" << wMergeInterval << "\"" << std::endl; + if (fields->fAllMemChanges) cwv << " changesSavedWin=\"" << fields->fAllMemChanges << "\"" << std::endl; + if (fields->fOnlySync) cwv << " onlySync=\"" << fields->fOnlySync << "\"" << std::endl; + if (fields->fPersonalView) cwv << " personalView=\"" << fields->fPersonalView << "\"" << std::endl; + if (!fields->fPrintIncl) cwv << " includePrintSettings=\"" << fields->fPrintIncl << "\"" << std::endl; + // wrong? + // if (!fields->fRowColIncl) cwv << " includeHiddenRowCol=\"" << fields->fRowColIncl << "\"" << std::endl; + if (fields->fZoom) cwv << " maximized=\"" << fields->fZoom << "\"" << std::endl; + if (fields->fIconic) cwv << " minimized=\"" << fields->fIconic << "\"" << std::endl; + if (!fields->fDspHScroll) cwv << " showHorizontalScroll=\"" << fields->fDspHScroll << "\"" << std::endl; + if (!fields->fDspVScroll) cwv << " showVerticalScroll=\"" << fields->fDspVScroll << "\"" << std::endl; + if (!fields->fBotAdornment) cwv << " showSheetTabs=\"" << fields->fBotAdornment << "\"" << std::endl; + if (xLeft > 0) cwv << " xWindow=\"" << xLeft << "\"" << std::endl; + if (yTop > 0) cwv << " yWindow=\"" << yTop << "\"" << std::endl; + if (xRight > 0) cwv << " windowWidth=\"" << xRight << "\"" << std::endl; + if ((yBot - yTop) > 0) cwv << " windowHeight=\"" << (yBot - yTop) << "\"" << std::endl; + if (iTabRatio != 600) cwv << " tabRatio=\"" << iTabRatio << "\"" << std::endl; + cwv << " activeSheetId=\"" << iTabid << "\"" << std::endl; + if (!fields->fDspFmlaBar) cwv << " showFormulaBar=\"" << fields->fDspFmlaBar << "\"" << std::endl; + if (!fields->fDspStatus) cwv << " showStatusbar=\"" << fields->fDspStatus << "\"" << std::endl; + if (showComments != "commIndicator") cwv << " showComments=\"" << showComments << "\"" << std::endl; + if (showObjects != "all") cwv << " showObjects=\"" << showObjects << "\"" << std::endl; + cwv << "/>" << std::endl; + + // Rcpp::Rcout << xLeft << ": " << xRight << ": " << yTop << ": " << yBot << std::endl; + + customWorkbookView.push_back(cwv.str()); + + break; + } + case BrtBeginBundleShs: { if (debug) Rcpp::Rcout << "" << std::endl; @@ -1941,6 +2075,17 @@ int32_t workbook_bin(std::string filePath, std::string outPath, bool debug) { } } + if (customWorkbookView.size()) { + out << "" << std::endl; + for (size_t i = 0; i < customWorkbookView.size(); ++i) { + if (debug) + Rcpp::Rcout << customWorkbookView[i] << std::endl; + out << customWorkbookView[i] << std::endl; + } + out << "" << std::endl; + } + + if (debug) Rcpp::Rcout << "" << std::endl; out << "" << std::endl; bin.seekg(size, bin.cur); @@ -2289,7 +2434,7 @@ int32_t worksheet_bin(std::string filePath, bool chartsheet, std::string outPath if (colLeft > 0 || rwTop > 0) out << " topLeftCell=\"" << int_to_col(colLeft + 1) << std::to_string(rwTop + 1) << "\""; if (xlView) - out << " view=\"" << xlView << "\""; + out << " view=\"" << XLView(xlView) << "\""; if (fields->fWnProt) out << " windowProtection=\"" << fields->fWnProt << "\""; if (wScale) @@ -2376,20 +2521,27 @@ int32_t worksheet_bin(std::string filePath, bool chartsheet, std::string outPath std::string stHeaderFirst = XLNullableWideString(bin, swapit); std::string stFooterFirst = XLNullableWideString(bin, swapit); + BrtBeginHeaderFooterFields *fields = (BrtBeginHeaderFooterFields *)&flags; + if (debug) Rcpp::Rcout << stHeader<< ": " << stFooter << ": " << stHeaderEven << ": " << stFooterEven << ": " << stHeaderFirst << ": " << stFooterFirst << std::endl; - out << "" << - "" << stHeader <<"" << - "" << stFooter <<"" << - "" << stHeaderFirst <<"" << - "" << stFooterFirst <<"" << - "" << stHeaderEven <<"" << - "" << stHeaderEven <<"" << - // "" << <<"" << - "" << std::endl; + out << "fHFDiffOddEven) out << " differentOddEven=\"" << fields->fHFDiffOddEven << "\"" << std::endl; + if (fields->fHFDiffFirst) out << " differentFirst=\"" << fields->fHFDiffFirst << "\"" << std::endl; + if (fields->fHFScaleWithDoc) out << " scaleWithDoc=\"" << fields->fHFScaleWithDoc << "\"" << std::endl; + if (fields->fHFAlignMargins) out << " alignWithMargins=\"" << fields->fHFAlignMargins << "\"" << std::endl; + out << ">" << std::endl; + + if (!stHeader.empty()) out << "" << stHeader <<"" << std::endl; + if (!stFooter.empty()) out << "" << stFooter <<"" << std::endl; + if (!stHeaderEven.empty()) out << "" << stHeaderEven <<"" << std::endl; + if (!stFooterEven.empty()) out << "" << stFooterEven <<"" << std::endl; + if (!stHeaderFirst.empty()) out << "" << stHeaderFirst <<"" << std::endl; + if (!stFooterFirst.empty()) out << "" << stFooterFirst <<"" << std::endl; + out << "" << std::endl; break; } @@ -3419,14 +3571,222 @@ int32_t worksheet_bin(std::string filePath, bool chartsheet, std::string outPath break; } + case BrtBeginCustomFilters: case BrtBeginCustomFilters14: case BrtBeginCustomRichFilters: { - if (debug) Rcpp::Rcout << "BrtBeginCustom..." << std::endl; - Rcpp::warning("Custom Filter found. This is not handled."); - bin.seekg(size, bin.cur); + int32_t fAnd = 0; + + // in xlsb it is flipped + fAnd = readbin(fAnd, bin, swapit) ^ 1; + + out << ""; + + break; + } + + case BrtCustomFilter: + case BrtCustomFilter14: + { + int8_t vts = 0, grbitSgn = 0; + double union_val = 0; + std::string vtsStringXls; + + vts = readbin(vts, bin, swapit); + grbitSgn = readbin(grbitSgn, bin, swapit); + if (vts == 4) { + // a double + union_val = Xnum(bin, swapit); + } else if (vts == 8) { + // a bool + int8_t boolean = 0; + boolean = readbin(boolean, bin, swapit); + union_val = static_cast(readbin(boolean, bin, swapit)); + for (int8_t blk = 0; blk < 7; ++blk) { + readbin(boolean, bin, swapit); + } + } else { + // ignore + readbin(union_val, bin, swapit); + readbin(union_val, bin, swapit); + } + + if (vts == 6) // a string + vtsStringXls = XLWideString(bin, swapit); + + out << ""; + + break; + } + + case BrtEndCustomFilters: + case BrtEndCustomRichFilters: + { + + out << "" << std::endl; + + break; + } + + case BrtBeginUserCsViews: + case BrtBeginUserShViews: + { + if (debug) Rcpp::Rcout << "BrtBeginUserXXViews" << std::endl; + out << "" << std::endl; + break; + } + + case BrtBeginUserCsView: + { + if (debug) Rcpp::Rcout << "BrtBeginUserCsView" << std::endl; + + int32_t guid0 = 0, guid1 = 0, guid2 = 0, guid3 = 0, iTabId = 0, dwScale = 0, flags = 0; + + std::vector guids(4); + guids[0] = readbin(guid0, bin, 0); + guids[1] = readbin(guid1, bin, 0); + guids[2] = readbin(guid2, bin, 0); + guids[3] = readbin(guid3, bin, 0); + + iTabId = readbin(iTabId, bin, swapit); + if (iTabId < 1 || iTabId > 65535) + Rcpp::stop("iTabId out of range"); + + dwScale = readbin(dwScale, bin, swapit); + if (dwScale < 0 || dwScale > 400) // dialog sheet 0 else 10 + Rcpp::stop("dwScale out of range"); + + flags = readbin(flags, bin, swapit); + // hsState + // fZoomToFit + + out << "" << std::endl; + + break; + } + + case BrtBeginUserShView: + { + if (debug) Rcpp::Rcout << "BrtBeginUserShView" << std::endl; + + int32_t guid0 = 0, guid1 = 0, guid2 = 0, guid3 = 0, iTabId = 0, dwScale = 0, icv = 0, flags = 0; + + std::vector guids(4); + guids[0] = readbin(guid0, bin, 0); + guids[1] = readbin(guid1, bin, 0); + guids[2] = readbin(guid2, bin, 0); + guids[3] = readbin(guid3, bin, 0); + + iTabId = readbin(iTabId, bin, swapit); + if (iTabId < 1 || iTabId > 65535) + Rcpp::stop("iTabId out of range"); + + dwScale = readbin(dwScale, bin, swapit); + if (dwScale < 0 || dwScale > 400) // dialog sheet 0 else 10 + Rcpp::stop("dwScale out of range"); + + icv = readbin(icv, bin, swapit); + if (icv > 64) + Rcpp::stop("icv out of range"); + + flags = readbin(flags, bin, swapit); + + // rfxTopLeft + std::vector rfx = UncheckedRfX(bin, swapit); + + BrtBeginUserShViewFields *fields = (BrtBeginUserShViewFields *)&flags; + + out << "fShowBrks) + out << " showPageBreaks=\"" << (int16_t)fields->fShowBrks << "\""; + if (fields->fDspFmlaSv) + out << " showFormulas=\"" << (int16_t)fields->fDspFmlaSv << "\""; + if (!fields->fDspGridSv) + out << " showGridLines=\"" << (int16_t)fields->fDspGridSv << "\""; + if (!fields->fDspRwColSv) + out << " showRowCol=\"" << (int16_t)fields->fDspRwColSv << "\""; + if (!fields->fDspGutsSv) + out << " outlineSymbols=\"" << (int16_t)fields->fDspGutsSv << "\""; + if (!fields->fDspZerosSv) + out << " zeroValues=\"" << (int16_t)fields->fDspZerosSv << "\""; + if (fields->fFitToPage) + out << " fitToPage=\"" << (int16_t)fields->fFitToPage << "\""; + if (fields->fPrintArea) + out << " printArea=\"" << (int16_t)fields->fPrintArea << "\""; + if (fields->fFilterMode) + out << " filter=\"" << (int16_t)fields->fFilterMode << "\""; + if (fields->fEzFilter) + out << " showAutoFilter=\"" << (int16_t)fields->fEzFilter << "\""; + // if (iTabId) // not used? + // out << " tabSelected=\"" << iTabId << "\""; + if (fields->fHiddenRw) + out << " hiddenRows=\"" << (int16_t)fields->fHiddenRw << "\""; + if (fields->fHiddenCol) + out << " hiddenColumns=\"" << (int16_t)fields->fHiddenCol << "\""; + if (fields->hsState) + out << " state=\"" << (int16_t)fields->hsState << "\""; + if (fields->fFilterUnique) + out << " filterUnique=\"" << (int16_t)fields->fFilterUnique << "\""; + if (fields->fSheetLayoutView) + out << " view=\"" << "pageBreakPreview" << "\""; + if (fields->fPageLayoutView) + out << " view=\"" << "pageLayout" << "\""; + if (!fields->fRuler) + out << " showRuler=\"" << (int16_t)fields->fRuler << "\""; + if (rfx[0] > 1 && rfx[2] > 0) + out << " topLeftCell=\"" << int_to_col(rfx[2] + 1) << std::to_string(rfx[0] + 1) << "\""; + + out << ">" << std::endl; + + // order matters for + out << "fHorizontal) + out << " horizontalCentered = \"" << (int16_t)fields->fHorizontal << "\""; + if (fields->fVertical) + out << " verticalCentered = \"" << (int16_t)fields->fVertical << "\""; + if (fields->fPrintRwCol) + out << " headings = \"" << (int16_t)fields->fPrintRwCol << "\""; + if (fields->fDspGridSv) + out << " gridLines = \"" << (int16_t)fields->fDspGridSv << "\""; + if (!fields->fPrintGrid) + out << " gridLinesSet = \"" << (int16_t)fields->fPrintGrid << "\""; + out << " />" << std::endl; + + break; + } + + case BrtEndUserCsView: + case BrtEndUserShView: + { + if (debug) Rcpp::Rcout << "BrtEndUserXXView" << std::endl; + out << "" << std::endl; + break; + } + + case BrtEndUserCsViews: + case BrtEndUserShViews: + { + if (debug) Rcpp::Rcout << "BrtEndUserXXViews" << std::endl; + out << "" << std::endl; break; } diff --git a/src/xlsb_defines.h b/src/xlsb_defines.h index 73e762dfd..2e8cb0551 100644 --- a/src/xlsb_defines.h +++ b/src/xlsb_defines.h @@ -219,6 +219,66 @@ typedef struct { uint8_t reserved2 : 2; } PtgListFields; +typedef struct { + bool fShowBrks : 1; + bool fDspFmlaSv : 1; + bool fDspGridSv : 1; + bool fDspRwColSv : 1; + bool fDspGutsSv : 1; + bool fDspZerosSv : 1; + bool fHorizontal : 1; + bool fVertical : 1; + bool fPrintRwCol : 1; + bool fPrintGrid : 1; + bool fFitToPage : 1; + bool fPrintArea : 1; + bool fOnePrintArea : 1; + bool fFilterMode : 1; + bool fEzFilter : 1; + bool reserved1 : 1; + bool reserved2 : 1; + bool fSplitV : 1; + bool fSplitH : 1; + uint8_t fHiddenRw : 2; + bool fHiddenCol : 1; + uint8_t hsState : 2; + bool reserved3 : 1; + bool fFilterUnique : 1; + bool fSheetLayoutView : 1; + bool fPageLayoutView : 1; + bool reserved4 : 1; + bool fRuler : 1; + bool reserved5 : 1; + bool reserved6 : 1; +} BrtBeginUserShViewFields; + +typedef struct { + bool fIconic : 1; + bool fDspHScroll : 1; + bool fDspVScroll : 1; + bool fBotAdornment : 1; + bool fZoom : 1; + bool fDspFmlaBar : 1; + bool fDspStatus : 1; + uint8_t mdDspNote : 2; + uint8_t mdHideObj : 2; + bool fPrintIncl : 1; + bool fRowColIncl : 1; + bool fTimedUpdate : 1; + bool fAllMemChanges : 1; + bool fOnlySync : 1; + bool fPersonalView : 1; + uint16_t: 15; +} BrtUserBookViewFields; + +typedef struct { + bool fHFDiffOddEven: 1; + bool fHFDiffFirst: 1; + bool fHFScaleWithDoc: 1; + bool fHFAlignMargins: 1; + uint16_t reserved: 12; +} BrtBeginHeaderFooterFields; + enum PtgRowType { data = 0x00, @@ -1220,6 +1280,15 @@ enum PtgStructure2 PtgAttrIfError = 0x80 }; +std::string XLView(const uint32_t val) { + switch(val) { + case 0x00000000: return "normal"; + case 0x00000001: return "pageBreakPreview"; + case 0x00000002: return "pageLayout"; + } + return ""; +} + // #nocov start // copied from the website std::string Cetab(const uint16_t val) { diff --git a/src/xlsb_funs.h b/src/xlsb_funs.h index 71e9b3f82..47e51702c 100644 --- a/src/xlsb_funs.h +++ b/src/xlsb_funs.h @@ -1045,6 +1045,19 @@ std::string typOperator(uint8_t oprtr) { return "unknown_operator"; } + +std::string grbitSgnOperator(uint8_t oprtr) { + + if (oprtr == 0x01) return "lessThan"; + if (oprtr == 0x02) return "equal"; + if (oprtr == 0x03) return "lessThanOrEqual"; + if (oprtr == 0x04) return "greaterThan"; + if (oprtr == 0x05) return "notEqual"; + if (oprtr == 0x06) return "greaterThanOrEqual"; + + return "unknown_operator"; +} + std::vector Xti(std::istream& sas, bool swapit) { int32_t firstSheet = 0, lastSheet = 0; int32_t externalLink = 0; // TODO actually uint32? @@ -1065,6 +1078,37 @@ std::vector Xti(std::istream& sas, bool swapit) { return out; } +// first half little endian, second half big endian +std::string guid_str(const std::vector& guid_ints) { + + std::ostringstream guidStream; + + guidStream << std::uppercase << std::hex << std::setfill('0'); + + guidStream << std::setw(2) << ((guid_ints[0] >> 24) & 0xFF) + << std::setw(2) << ((guid_ints[0] >> 16) & 0xFF) + << std::setw(2) << ((guid_ints[0] >> 8) & 0xFF) + << std::setw(2) << (guid_ints[0] & 0xFF) << "-"; + + guidStream << std::setw(2) << ((guid_ints[1] >> 8) & 0xFF) + << std::setw(2) << (guid_ints[1] & 0xFF) << "-"; + + guidStream << std::setw(2) << ((guid_ints[1] >> 24) & 0xFF) + << std::setw(2) << ((guid_ints[1] >> 16) & 0xFF) << "-"; + + guidStream << std::setw(2) << (guid_ints[2] & 0xFF) + << std::setw(2) << ((guid_ints[2] >> 8) & 0xFF) << "-" + << std::setw(2) << ((guid_ints[2] >> 16) & 0xFF) + << std::setw(2) << ((guid_ints[2] >> 24) & 0xFF) + << std::setw(2) << (guid_ints[3] & 0xFF) + << std::setw(2) << ((guid_ints[3] >> 8) & 0xFF) + << std::setw(2) << ((guid_ints[3] >> 16) & 0xFF) + << std::setw(2) << ((guid_ints[3] >> 24) & 0xFF) + ; + + return guidStream.str(); +} + // bool isOperator(const std::string& token) { // return token == "+" || token == "-" || token == "*" || token == "/" || // token == "^" || token == "%" || token == "=" || token == "<>" || diff --git a/tests/testthat/test-read_xlsb.R b/tests/testthat/test-read_xlsb.R index e1ea68573..9d2e7e190 100644 --- a/tests/testthat/test-read_xlsb.R +++ b/tests/testthat/test-read_xlsb.R @@ -163,3 +163,16 @@ test_that("shared formulas are detected correctly", { ) }) + +test_that("loading custom sheet view in xlsb files works", { + + skip_online_checks() + + fl <- testfile_path("custom_sheet_view.xlsb") + + wb <- wb_load(fl) + + exp <- "" + got <- wb$worksheets[[1]]$customSheetViews + expect_equal(exp, got) +})