Teknik Makale

Excel #SPILL! ve #CALC! Önbelleklerini Neden Yok Sayar

Excel, önbelleklenmiş bir #SPILL! ya da #CALC!e yalnızca workbook tam modern hata sözleşmesini taşıdığında güvenir: XLRICHVALUE uzantı URI'si küçük harfli GUID olan Rich Data meta verisi, 7 değerde bir fileVersion, en az 191029 olan bir calcId ve microsoft.com:RD hesaplama özelliği. Yerel Delphi XLSX bileşeni olan HotXLS, kaydedilen bir hücre bu hatalardan birini taşıdığında dördünü birden üretir

Bir parçayı kaçırın ve başarısızlık nadiren gürültülü olur. Bazen Excel 16 dosyayı hiç açmayı reddeder. Daha sık olarak sorunsuz açar ve hesaplama motorunuzun #SPILL! ürettiği yerde #VALUE! gösterir; bu, hesaplama hatası gibi görünür ve kusursuz doğru olan formül kodunda avlanmanıza yol açar. F9'a basın ve doğru hata görünür; sorun, bir müşteri raporu otomatik hesaplama kapalıyken açana dek "benim makinemde çalışıyor" diye kapanma eğilimindedir. Bu yazı her parçanın ne yaptığını, önemsiz olamayacak gibi görünen GUID harf durumu tuzağını ve tam olarak aynı sessiz şekilde başarısız olan dizi metin önbelleği kuralını anlatır

Excel, önbelleklenmiş #SPILL! yerine neden #VALUE! gösterir?

Excel #VALUE! gösterir; çünkü hücre harfiyen onu saklar. ECMA-376 SpreadsheetML'deki eski hata türü sabit bir hata literal listesine sahiptir ve #SPILL! ile #CALC! listede yoktur. t="e" taşıyan bir hücre yalnızca #NULL!, #DIV/0!, #VALUE!, #REF!, #NAME?, #NUM! ya da #N/A diyebilir; bu yüzden yeni Excel sürümleri geriye dönük uyumlu yedek olarak #VALUE! yazar ve gerçek hata türünü bir value-metadata indeksine asar

O indeks, vm özniteliği, xl/metadata.xmle işaret eder; o da gerçek hatayı betimleyen bir Rich Data değerine işaret eder. Eski okuyucular #VALUE! görür ve yoluna devam eder. Güncel Excel zinciri izler ve modern hatayı gösterir; ama yalnızca zinciri güvenilir bulduğunda. Diskte bir taşmış-ve-engellenmiş formül şöyle görünür:

<!-- sheet1.xml: eski literal artı bir value-metadata işaretçisi -->
<c r="A1" t="e" cm="1" vm="1">
  <f t="array" ref="A1">_xlfn.SEQUENCE(3)</f>
  <v>#VALUE!</v>
</c>

<!-- xl/richData/rdrichvaluestructure.xml -->
<s t="_error">
  <k n="errorType" t="i"/>
  <k n="colOffset" t="i"/>
  <k n="rwOffset" t="i"/>
</s>

<!-- xl/richData/rdrichvalue.xml: errorType 8, #SPILL! demektir -->
<rv s="0"><v>8</v><v>0</v><v>2</v></rv>

HotXLS, #SPILL! için sütun ve satır kaymalarıyla hata türü 8'i ve #CALC! için kaymasız hata türü 13'ü yazar. Eski <v>, her iki durumda da bilerek #VALUE! kalır; çünkü Rich Data'yı yok sayan her okuyucunun görmesi beklenen değer odur

Excel 16'nın gerçekte denetlediği küçük harfli GUID

XLRICHVALUE future-metadata bloğu, uzantı URI'si olarak {3e2802c4-a4d2-4d8b-9148-e3be6c30e623} değerini harfi harfine küçük harfle kullanmak zorundadır. Hex harflerini büyük harfe çevirin ve yerel Excel 16 derlemesi workbooku normal açmayı reddetti; her XML parçası ayrıştırılırken, her relationship çözülürken ve her sayım ile indeks tutarken bile

<futureMetadata name="XLRICHVALUE" count="1">
  <bk>
    <extLst>
      <ext uri="{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}">
        <xlrd:rvb i="0"/>
      </ext>
    </extLst>
  </bk>
</futureMetadata>
<valueMetadata count="1">
  <bk><rc t="2" v="0"/></bk>
</valueMetadata>

COM ya da registry ile çalışmış herkes GUID'lerin büyük/küçük harfe duyarsız karşılaştırıldığını bilir ve tam da o alışkanlık burada ısırır. Biz onu yavaş yolla izole ettik: Excel'in reddettiği minimal bir paket kurduk, sonra bir seferde tek bir şeyi değiştirdik. Content type'ları değiştirmek çözmedi. Bir rich value types parçası eklemek çözmedi. Namespace öneklerini değiştirmek ya da rich value verisini yeniden yazmak çözmedi. O tek uri özniteliğini küçük harfe çevirmek çözdü ve aynı paket sonra açıp hem #CALC!i hem #SPILL!i gösterdi. Uzantı URI'sini opak bir string literal gibi ele alın, biçim tanımından kopyalayın ve kendi harf durumunu seçen bir GUID formatlayıcısından asla geçirmeyin

Tanı paketlerini elle yazan herkes için pratik bir sonuç vardır. Düzenlemeleri serileştirilmiş XML üzerinde genel metin değişimiyle değil, bir XML ayrıştırıcısıyla yapın. Tek bir token'a yönelik toplu bir değiştirme, parçanın başka bir yerindeki &gt; gibi entity'leri bozabilir; sonra özgün soru yerine bozuk bir probe'u hata ayıklarsınız

Excel, önbelleklenmiş modern bir hatayı okumak için başka neye ihtiyaç duyar?

Meta veri parçaları ve relationship'leri gereklidir ama yeterli değildir. Kusursuz geçerli bir Rich Data zinciri yerindeyken Excel, workbook parçası kendini modern bir hesaplama motorundan geliyor olarak bildirene dek önbelleklenmiş hatayı yok saydı. Kaydedilen bir hücre #SPILL! ya da #CALC! taşıdığında HotXLS, workbook.xmle üç bildirim yazar:

<fileVersion appName="xl" lastEdited="7" lowestEdited="7"/>
...
<calcPr calcId="191029"/>
<extLst>
  <ext uri="...">
    <calcFeatures>
      <feature name="microsoft.com:RD"/>
    </calcFeatures>
  </ext>
</extLst>

fileVersion elemanı düpedüz ECMA-376 Part 1 workbook meta verisidir ve 7 olan lastEdited / lowestEdited, dosyayı en son Rich Data'yı anlayan bir uygulama neslinin kaydettiğini Excel'e söyler. calcPr üzerindeki calcId hesaplama motoru sürümünü tanımlar ve 191029, Excel'in bu önbellekler için kabul ettiğini bulduğumuz asgari değerdir. http://schemas.microsoft.com/office/spreadsheetml/2018/calcfeatures namespace'indeki hesaplama özellikleri uzantısındaki microsoft.com:RD girişi, önbelleklenmiş değerleri üreten motorun Rich Data semantiğiyle değerlendirildiğini bildirir. Yalnızca alt türü vermek, yalnızca types parçasını vermek ya da yalnızca farklı önekler ve relationship yolları sunmak sonucu değiştirmedi. Üç bildirimin de birlikte var olması gerekiyordu

Bu değerleri kendiniz ayarlıyorsanız HotXLS'in onları uygulama biçimindeki üç ayrıntıyı bilmeye değer:

  • TXLSXWorkbook.CalcId üzerine yazılmaz. Yazıcı üretilen değeri yalnızca çıktıda 191029'a yükseltir; böylece daha yüksek bir CalcId yapılandıran çağıran onu korur ve bellek içi özellik atadığınız şeyin aynısı olarak kalır
  • Açılan bir dosyayla gelen mevcut hesaplama özellikleri ve ilgisiz workbook uzantıları korunur, değiştirilmez
  • Yazıcı, eklemeden önce mevcut bir microsoft.com:RD özelliğini denetler; böylece aynı workbooku tekrar tekrar kaydetmek asla çift bildirim yığmaz
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('forecast.xlsx');
    Sheet := Book.Sheets[1];
    // A1 üç satıra taşar; A2 dolu, bu yüzden sonuç #SPILL!
    Sheet.Range['A1'].SetDynamicArrayFormula('=SEQUENCE(3)');
    Sheet.Cells[2, 1].Value := 'blocker';
    Book.Recalculate;
    // Daha yüksek bir CalcId hayatta kalır; yazıcı yalnızca düşükse 191029'a yükseltir
    Book.CalcId := 191029;
    Book.SaveAs('forecast-out.xlsx');   // rich hata parçaları otomatik yazılır
  finally
    Book.Free;
  end;
end;

Rich Data parçalarını asla kendiniz kurmazsınız. Yazmadan önce TXLSXWorkbook, her hücreyi #SPILL! ya da #CALC! değerleri için tarar, xl/metadata.xml ile iki rich value parçasını kurar ya da genişletir ve hatası bu arada değişmiş ya da temizlenmiş hücrelerden bayat value-metadata bağlantılarını düşürür. Yukarıdaki açık CalcId ataması isteğe bağlıdır ve yalnızca ona saygı gösterildiğini göstermek için vardır

Dizi metin önbellekleri t="str"e aittir, shared string'lere değil

Aynı sessiz başarısızlık, dizilerin içindeki metin sonuçlarda da ortaya çıkar. HotXLS, bir formül hücresinin ve bir CSE ya da dynamic array aralığındaki her izleyici hücrenin önbelleklenmiş metnini, literal metin <v> içinde olacak şekilde t="str" olarak kaydeder. O önbellekleri t="s" ile shared string olarak yazmak, Excel önbelleklenmiş dosyayı okurken dizi hücrelerinde #VALUE! göstermesine yol açar

<!-- Doğru: bir dizi izleyicisinde literal metin önbelleği -->
<c r="B3" t="str"><v>Northwind</v></c>

<!-- Yanlış: shared-string indeksi; Excel önbellekten #VALUE! gösterir -->
<c r="B3" t="s"><v>17</v></c>

Bu hata kolay yapılır; çünkü t="s", sıradan değer hücreleri için doğru tercihtir ve çoğu yazıcı her metni varsayılan olarak shared string tablosundan geçirir. Tuzak doğrulamadadır. Excel'de yeniden hesaplayın ve dizi hücreleri doğru metni gösterir; bu, formülün sağlam olduğunu kanıtlar ama yazdığınız önbellek hakkında hiçbir şey kanıtlamaz. Kullanıcılarınız raporları elle hesaplamayla açıyorsa ya da akıştaki bir araç, hesaplamadan önbelleklenmiş değerleri okuyorsa shared-stringli sürüm düpedüz bozuktur

Formül saklama sözdizimi: baştaki eşittir, virgüller ve _xlws

Aynı yazıcıya ilgili bir temizlik daha giriyor: OOXML'e giden formüller, kullanıcının yazdığı dil bilgisinde değil, dosya dil bilgisinde saklanır. HotXLS, Formulaya atadığınız metni gidiş dönüş görüntüsü için olduğu gibi tutar; ama serileştirilen formül baştaki =ı atar, fonksiyon argümanları arasında virgül kullanır ve dizi sabiti satır ayraçları ile tırnaklı metne dokunmaz. FILTER ile SORT, Excel'in o yalnızca-worksheet fonksiyonları için beklediği _xlfn._xlws. önekiyle saklanır; SEQUENCE gibi diğer future fonksiyonları ise düz _xlfn. önekini alır

// Noktalı virgül ayraçlarıyla yazıldı, şöyle saklandı:
//   _xlfn._xlws.FILTER(A2:A50,C2:C50>1000)
Sheet.Range['E2'].SetDynamicArrayFormula('=FILTER(A2:A50;C2:C50>1000)');
Book.Recalculate;   // 1000 üstü satır yok #CALC! verir; Rich Data ile kaydedilir

Yazıcının içinde sıra önemlidir. XML kaçışlaması sözdizimi normalizasyonundan sonra çalışır, asla önce; tanı paketlerinin ayrıştırıcı istemesiyle aynı nedenle: kaçışlanmış metne uygulanacak bir ayraç yeniden yazımı, karşılaştırma operatörlerindeki &gt; gibi entity'leri paramparça ederdi

Kendini kandırmadan önbelleklenmiş bir hatayı nasıl doğrularsın

Güvenilir tek test, dosyayı Excel'de hesaplama önceden elle moda alınmış olarak açmak, hücreleri okumak ve ancak sonra yeniden hesaplamaktır. Excel hesaplama modunu, bir oturumda açılan ilk workbooktan devralır; bu yüzden kabul test düzeneğimiz önce kendi tohum workbookunu kurar, onu elle moda alır, sonra test edilen dosyayı açar. Tohum adımını atlayan probe'lar yalnızca dosyanın açılıp doğru hesaplandığını kanıtladı. Önbelleklenmiş diye etiketledikleri her şey çoktan yeniden hesaplanmıştı

O ayrım, bütün yazının minyatürüdür. Modern bir hata önbelleği, herhangi bir hesaplama çalışmadan Excel onu gösterdiğinde doğrudur ve yukarıdaki her gereklilik — küçük harfli GUID'den t="str" metin izleyicilerine — yeniden hesaplanmış durumu değil o durum denetlenerek bulundu. HotXLS'in spill aralıklarını en başta nasıl boyutlandırdığına ve sahiplendiğine dair arka plan için HotXLS ile Delphi'de dynamic array spill formülleri yazısına bakın. Belirtiniz yanlış bir hata değil bir onarım istemiyorsa Excel'in geçerli bir XLSX'i neden onardığı yazısındaki paket seviyesi kurallar daha iyi bir başlangıç noktasıdır

FAQ

HotXLS, #SPILL! ile #CALC! hatalarını doğru kaydetmek için bir ayara ihtiyaç duyar mı?

Hayır. HotXLS, #SPILL! ile #CALC! değerlerini kayıt anında saptar ve Rich Data parçalarını, fileVersionu, asgari calcIdyi ve microsoft.com:RD özelliğini otomatik yazar

Hücre XML'i neden hâlâ #VALUE! içeriyor?

Eski SpreadsheetML hata türünün #SPILL! ya da #CALC! için bir literal'i yoktur; bu yüzden #VALUE! geriye dönük uyumlu yedektir. Güncel Excel gerçek hatayı vm value-metadata bağlantısı üzerinden okur

HotXLS, kendimin ayarladığı bir CalcId'yi düşürür mü?

Hayır. TXLSXWorkbook.CalcId, yazılan dosyada yalnızca modern bir hata varken 191029'a yükseltilir ve yapılandırdığınız daha yüksek bir değer olduğu gibi korunur

HotXLS, XLSX, XLSM ve eski XLS'i Delphi ve C++Builder'dan, dynamic array'ler dâhil, kendi hesaplama motoruyla okur ve yazar. Ayrıntılar ve deneme indirmesi HotXLS Delphi spreadsheet component sayfasında