filters

ExportCss.cc

00001 /*
00002    This file is part of the KDE project
00003    Copyright (C) 2001, 2002, 2004 Nicolas GOUTTE <goutte@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include <qstring.h>
00022 #include <qtextcodec.h>
00023 #include <qfile.h>
00024 
00025 #include <klocale.h>
00026 #include <kdebug.h>
00027 
00028 #include <KWEFUtil.h>
00029 #include <KWEFBaseWorker.h>
00030 
00031 #include "ExportFilter.h"
00032 #include "ExportCss.h"
00033 
00034 QString HtmlCssWorker::escapeCssIdentifier(const QString& strText) const
00035 {
00036     // Reference: section 4.1.3 of the CSS2 recommendation
00037     // However most HTML user agents support this section only in a restrictive way, so we cannot use any CSS escape
00038 
00039     // NOTE: we do not guarantee anymore that the style name is unique! (### FIXME)
00040 
00041     QString strReturn;
00042 
00043     // Taken in the restrictive way, an identifier can only start with a letter.
00044     const QChar qch0(strText[0]);
00045     if ((qch0<'a' || qch0>'z') && (qch0<'A' || qch0>'Z'))
00046     {
00047         // Not a letter, so we have to add a prefix
00048         strReturn+="kWoRd_"; // The curious spelling is for allowing a HTML import to identfy it and to remove it.
00049         // The processing of the character itself is done below
00050     }
00051 
00052     for (uint i=0; i<strText.length(); i++)
00053     {
00054         const QChar qch(strText.at(i));
00055         const ushort ch=qch.unicode();
00056 
00057         if (((ch>='a') && (ch<='z'))
00058             || ((ch>='A') && (ch<='Z'))
00059             || ((ch>='0') && (ch<='9'))
00060             || (ch=='-') || (ch=='_')) // The underscore is allowed by the CSS2 errata
00061         {
00062             // Normal allowed characters (without any problem)
00063             strReturn+=qch;
00064         }
00065         else if ((ch<=' ') || (ch>=128 && ch<=160)) // space (breaking or not) and control characters
00066         {
00067             // CSS2 would allow to escape it but not any HTML user agent supports this
00068             strReturn+='_';
00069         }
00070         else if ((ch>=161) && (getCodec()->canEncode(qch)))
00071         {
00072             // Any Unicode character greater or egual to 161 is allowed
00073             // except if it cannot be written in the encoding
00074             strReturn+=qch;
00075         }
00076         else // if ch >= 33 && ch <=127 with holes (or not in encoding)
00077         {
00078             // Either CSS2 does not allow this character unescaped or it is not in the encoding
00079             // but a CSS escape would break some HTML user agents (e.g. Mozilla 1.4)
00080             // So we have to do our own incompatible cooking. :-(
00081             strReturn+="--"; // start our private escape
00082             strReturn+=QString::number(ch,16);
00083             strReturn+="--"; // end our private escape
00084         }
00085     }
00086     return strReturn;
00087 }
00088 
00089 QString HtmlCssWorker::textFormatToCss(const TextFormatting& formatOrigin,
00090     const TextFormatting& formatData, const bool force) const
00091 {
00092     // TODO: as this method comes from the AbiWord filter,
00093     // TODO:   verify that it is working for HTML
00094 
00095     // TODO: rename variable formatData
00096     QString strElement; // TODO: rename this variable
00097 
00098     // Font name
00099     QString fontName = formatData.fontName;
00100     if (!fontName.isEmpty()
00101         && (force || (formatOrigin.fontName!=formatData.fontName)))
00102     {
00103         strElement+="font-family: ";
00104         if (fontName.find(' ')==-1)
00105             strElement+=escapeHtmlText(fontName);
00106         else
00107         {   // If the font name contains a space, it should be quoted.
00108             strElement+='\'';
00109             strElement+=escapeHtmlText(fontName);
00110             strElement+='\'';
00111         }
00112         // ### TODO: add alternative font names
00113         strElement+="; ";
00114     }
00115 
00116     if (force || (formatOrigin.italic!=formatData.italic))
00117     {
00118         // Font style
00119         strElement+="font-style: ";
00120         if ( formatData.italic )
00121         {
00122             strElement+="italic";
00123         }
00124         else
00125         {
00126             strElement+="normal";
00127         }
00128         strElement+="; ";
00129     }
00130 
00131     if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75)))
00132     {
00133         strElement+="font-weight: ";
00134         if ( formatData.weight >= 75 )
00135         {
00136             strElement+="bold";
00137         }
00138         else
00139         {
00140             strElement+="normal";
00141         }
00142         strElement+="; ";
00143     }
00144 
00145     if (force || (formatOrigin.fontSize!=formatData.fontSize))
00146     {
00147         const int size=formatData.fontSize;
00148         if (size>0)
00149         {
00150             // We use absolute font sizes.
00151             strElement+="font-size: ";
00152             strElement+=QString::number(size,10);
00153             strElement+="pt; ";
00154         }
00155     }
00156 
00157     if (force || (formatOrigin.fgColor!=formatData.fgColor))
00158     {
00159         if ( formatData.fgColor.isValid() )
00160         {
00161             // Give colour
00162             strElement+="color: ";
00163             strElement+=formatData.fgColor.name();
00164             strElement+="; ";
00165         }
00166     }
00167 
00168     if (force || (formatOrigin.bgColor!=formatData.bgColor))
00169     {
00170         if ( formatData.bgColor.isValid() )
00171         {
00172             // Give background colour
00173             strElement+="background-color: ";
00174             strElement+=formatData.bgColor.name();
00175             strElement+="; ";
00176         }
00177     }
00178 
00179     if (force || (formatOrigin.underline!=formatData.underline)
00180         || (formatOrigin.strikeout!=formatData.strikeout))
00181     {
00182         strElement+="text-decoration: ";
00183         if ( formatData.underline )
00184         {
00185             strElement+="underline";
00186         }
00187         else if ( formatData.strikeout )
00188         {
00189             strElement+="line-through";
00190         }
00191         else
00192         {
00193             strElement+="none";
00194         }
00195         strElement+="; ";
00196     }
00197 
00198     if (force || (formatOrigin.fontAttribute!=formatData.fontAttribute))
00199     {
00200         bool smallcaps=false;
00201         strElement+="text-transform: ";
00202         if ( formatData.fontAttribute=="uppercase" )
00203         {
00204             strElement+="uppercase";
00205         }
00206         else if ( formatData.fontAttribute=="lowercase" )
00207         {
00208             strElement+="lowercase";
00209         }
00210         else if ( formatData.fontAttribute=="smallcaps" )
00211         {
00212             strElement+="none";
00213             smallcaps=true;
00214         }
00215         else
00216         {
00217             strElement+="none";
00218         }
00219         strElement+="; ";
00220         // ### TODO: mostly issuing font-variant is not necessary.
00221         strElement+="font-variant:";
00222         if (smallcaps)
00223             strElement+="small-caps";
00224         else
00225             strElement+="normal";
00226         strElement+="; ";
00227     }
00228 
00229     // TODO: As this is the last property, do not put a semi-colon
00230 
00231     return strElement;
00232 }
00233 
00234 QString HtmlCssWorker::getStartOfListOpeningTag(const CounterData::Style typeList, bool& ordered)
00235 {
00236     QString strResult;
00237     switch (typeList)
00238     {
00239     case CounterData::STYLE_CUSTOMBULLET: // We cannot keep the custom type/style
00240     default:
00241         {
00242             ordered=false;
00243             strResult="<ul>\n";
00244             break;
00245         }
00246     case CounterData::STYLE_NONE:
00247         {
00248             ordered=false;
00249             strResult="<ul style=\"list-style-type:none\">\n";
00250             break;
00251         }
00252     case CounterData::STYLE_CIRCLEBULLET:
00253         {
00254             ordered=false;
00255             strResult="<ul style=\"list-style-type:circle\">\n";
00256             break;
00257         }
00258     case CounterData::STYLE_SQUAREBULLET:
00259         {
00260             ordered=false;
00261             strResult="<ul style=\"list-style-type:square\">\n";
00262             break;
00263         }
00264     case CounterData::STYLE_DISCBULLET:
00265         {
00266             ordered=false;
00267             strResult="<ul style=\"list-style-type:disc\">\n";
00268             break;
00269         }
00270     case CounterData::STYLE_NUM:
00271         {
00272             ordered=true;
00273             strResult="<ol style=\"list-style-type:decimal\">\n";
00274             break;
00275         }
00276     case CounterData::STYLE_ALPHAB_L:
00277         {
00278             ordered=true;
00279             strResult="<ol style=\"list-style-type:lower-alpha\">\n";
00280             break;
00281         }
00282     case CounterData::STYLE_ALPHAB_U:
00283         {
00284             ordered=true;
00285             strResult="<ol style=\"list-style-type:upper-alpha\">\n";
00286             break;
00287         }
00288     case CounterData::STYLE_ROM_NUM_L:
00289         {
00290             ordered=true;
00291             strResult="<ol style=\"list-style-type:lower-roman\">\n";
00292             break;
00293         }
00294     case CounterData::STYLE_ROM_NUM_U:
00295         {
00296             ordered=true;
00297             strResult="<ol style=\"list-style-type:upper-roman\">\n";
00298             break;
00299         }
00300     case CounterData::STYLE_CUSTOM:
00301         {
00302             // We cannot keep the custom type/style
00303             ordered=true;
00304             strResult="<ol>\n";
00305             break;
00306         }
00307     }
00308     return strResult;
00309 }
00310 
00311 QString HtmlCssWorker::layoutToCss(const LayoutData& layoutOrigin,
00312     const LayoutData& layout, const bool force) const
00313 {
00314     QString strLayout;
00315 
00316     if (force || (layoutOrigin.alignment!=layout.alignment))
00317     {
00318         if ( (layout.alignment=="left") || (layout.alignment== "right")
00319             || (layout.alignment=="center") || (layout.alignment=="justify"))
00320         {
00321             strLayout += QString("text-align:%1; ").arg(layout.alignment);
00322         }
00323         else if ( layout.alignment=="auto")
00324         {
00325             // Do nothing, the user-agent should be more intelligent than us.
00326         }
00327         else
00328         {
00329             kdWarning(30503) << "Unknown alignment: " << layout.alignment << endl;
00330         }
00331     }
00332 
00333     if ((layout.indentLeft>=0.0)
00334         && (force || (layoutOrigin.indentLeft!=layout.indentLeft)))
00335     {
00336             strLayout += QString("margin-left:%1pt; ").arg(layout.indentLeft);
00337     }
00338 
00339     if ((layout.indentRight>=0.0)
00340         && (force || (layoutOrigin.indentRight!=layout.indentRight)))
00341     {
00342         strLayout += QString("margin-right:%1pt; ").arg(layout.indentRight);
00343     }
00344 
00345     if (force || (layoutOrigin.indentLeft!=layout.indentLeft))
00346     {
00347         strLayout += QString("text-indent:%1pt; ").arg(layout.indentFirst);
00348     }
00349 
00350     if ((layout.marginBottom>=0.0)
00351         && ( force || ( layoutOrigin.marginBottom != layout.marginBottom ) ) )
00352     {
00353        strLayout += QString("margin-bottom:%1pt; ").arg(layout.marginBottom);
00354     }
00355 
00356     if ((layout.marginTop>=0.0)
00357         && ( force || ( layoutOrigin.marginTop != layout.marginTop ) ) )
00358     {
00359        strLayout += QString("margin-top:%1pt; ").arg(layout.marginTop);
00360     }
00361 
00362     if (force
00363         || ( layoutOrigin.lineSpacingType != layout.lineSpacingType )
00364         || ( layoutOrigin.lineSpacing != layout.lineSpacing ) )
00365     {
00366         switch ( layout.lineSpacingType )
00367         {
00368         case LayoutData::LS_CUSTOM:
00369             { 
00370                 // ### TODO: CSS 2 does not known "at-least".
00371 #if 0
00372                 // We have a custom line spacing (in points)
00373                 const QString height ( QString::number(layout.lineSpacing) ); // ### TODO: rounding?
00374                 strLayout += "style:line-spacing:";
00375                 strLayout += height;
00376                 strLayout += "pt; ";
00377 #endif
00378                 break;          
00379             }
00380         case LayoutData::LS_SINGLE:
00381             {
00382                 strLayout += "line-height:normal; "; // One
00383                 break;
00384             }
00385         case LayoutData::LS_ONEANDHALF:
00386             {
00387                 strLayout += "line-height:150%; "; // One-and-half
00388                 break;
00389             }
00390         case LayoutData::LS_DOUBLE:
00391             {
00392                 strLayout += "line-height:200%; "; // Two
00393                 break;
00394             }
00395         case LayoutData::LS_MULTIPLE:
00396             {
00397                 const QString mult ( QString::number( qRound( layout.lineSpacing * 100 ) ) );
00398                 strLayout += "line-height:";
00399                 strLayout += mult;
00400                 strLayout += "%; ";
00401                 break;
00402             }
00403         case LayoutData::LS_FIXED:
00404             {
00405                 // We have a fixed line height (in points)
00406                 const QString height ( QString::number(layout.lineSpacing) ); // ### TODO: rounding?
00407                 strLayout += "line-height:";
00408                 strLayout += height;
00409                 strLayout += "pt; ";
00410                 break;
00411             }
00412         case LayoutData::LS_ATLEAST:
00413             {
00414                 // ### TODO: CSS 2 does not known "at-least".
00415                 // ### TODO:  however draft CCS3 (module 'line') has 'line-stacking-strategy' to tweak this behaviour
00416                 // We have a at-least line height (in points)
00417                 const QString height ( QString::number(layout.lineSpacing) ); // ### TODO: rounding?
00418                 strLayout += "line-height:";
00419                 strLayout += height;
00420                 strLayout += "pt; ";
00421                 break;
00422             }
00423         default:
00424             {
00425                 kdWarning(30503) << "Unsupported lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl;
00426                 break;
00427             }
00428         }
00429     }
00430 
00431     // TODO: Konqueror/KHTML does not support "text-shadow"
00432     if (!force
00433         && ( layoutOrigin.shadowDirection == layout.shadowDirection )
00434         && ( layoutOrigin.shadowDistance == layout.shadowDistance ) )
00435     {
00436         // Do nothing!
00437     }
00438     else if ((!layout.shadowDirection) || (!layout.shadowDistance))
00439     {
00440         strLayout += "text-shadow:";
00441         strLayout+="none; ";
00442     }
00443     else
00444     {
00445         double xDistance,yDistance;
00446         const double distance=layout.shadowDistance;
00447         switch (layout.shadowDirection)
00448         {
00449         case 1: // SD_LEFT_UP
00450             {
00451                 xDistance= (-distance);
00452                 yDistance= (-distance);
00453                 break;
00454             }
00455         case 2: // SD_UP
00456             {
00457                 xDistance= 0;
00458                 yDistance= (-distance);
00459                 break;
00460             }
00461         case 3: // SD_RIGHT_UP
00462             {
00463                 xDistance= (distance);
00464                 yDistance= (-distance);
00465                 break;
00466             }
00467         case 4: // SD_RIGHT
00468             {
00469                 xDistance= (distance);
00470                 yDistance= 0;
00471                 break;
00472             }
00473         case 5: // SD_RIGHT_BOTTOM
00474             {
00475                 xDistance= (distance);
00476                 yDistance= (distance);
00477                 break;
00478             }
00479         case 6: // SD_BOTTOM
00480             {
00481                 xDistance= 0;
00482                 yDistance= (distance);
00483                 break;
00484             }
00485         case 7: // SD_LEFT_BOTTOM
00486             {
00487                 xDistance= (-distance);
00488                 yDistance= (distance);
00489                 break;
00490             }
00491         case 8: // SD_LEFT
00492             {
00493                 xDistance= (distance);
00494                 yDistance= 0;
00495                 break;
00496             }
00497         default:
00498             {
00499                 xDistance=0;
00500                 yDistance=0;
00501                 break;
00502             }
00503         }
00504         if ( (!xDistance) && (!yDistance) )
00505         {
00506             strLayout += "text-shadow:";
00507             strLayout+="none; ";
00508         }
00509         else
00510         {
00511             strLayout += "text-shadow:";
00512             strLayout+=QString("%1 %2pt %3pt; ").arg(layout.shadowColor.name())
00513                 .arg(xDistance,0,'f',0).arg(yDistance,0,'f',0);
00514                 // We do not want any scientific notation or any decimal
00515         }
00516     }
00517 
00518     // TODO: borders
00519 
00520     // This must remain last, as the last property does not have a semi-colon
00521     strLayout+=textFormatToCss(layoutOrigin.formatData.text,
00522         layout.formatData.text,force);
00523 
00524     return strLayout;
00525 }
00526 
00527 void HtmlCssWorker::openParagraph(const QString& strTag,
00528     const LayoutData& layout, QChar::Direction direction)
00529 {
00530     const LayoutData& styleLayout=m_styleMap[layout.styleName];
00531 
00532     *m_streamOut << '<' << strTag;
00533 
00534     // Opening elements
00535     *m_streamOut << " class=\"" << escapeCssIdentifier(layout.styleName);
00536     *m_streamOut << "\"";
00537 
00538     QString strStyle=layoutToCss(styleLayout,layout,false);
00539     if (!strStyle.isEmpty())
00540     {
00541         *m_streamOut << " style=\"" << strStyle;
00542         if (direction == QChar::DirRLE) {
00543             *m_streamOut << "direction: rtl; unicode-bidi: embed; ";
00544         } else if (direction == QChar::DirRLO) {
00545             *m_streamOut << "direction: rtl; unicode-bidi: override; ";
00546         }
00547         *m_streamOut<< "\"";
00548     }
00549 
00550     *m_streamOut << ">";
00551 
00552     if ( 1==layout.formatData.text.verticalAlignment )
00553     {
00554         *m_streamOut << "<sub>"; //Subscript
00555     }
00556     else if ( 2==layout.formatData.text.verticalAlignment )
00557     {
00558         *m_streamOut << "<sup>"; //Superscript
00559     }
00560     if ( layout.alignment == "center" ) *m_streamOut << "<center>";
00561 }
00562 
00563 void HtmlCssWorker::closeParagraph(const QString& strTag,
00564     const LayoutData& layout)
00565 {
00566     if ( 2==layout.formatData.text.verticalAlignment )
00567     {
00568         *m_streamOut << "</sup>"; //Superscript
00569     }
00570     else if ( 1==layout.formatData.text.verticalAlignment )
00571     {
00572         *m_streamOut << "</sub>"; //Subscript
00573     }
00574 
00575     if ( layout.alignment == "center" ) *m_streamOut << "</center>";
00576     *m_streamOut << "</" << strTag << ">\n";
00577 }
00578 
00579 void HtmlCssWorker::openSpan(const FormatData& formatOrigin, const FormatData& format)
00580 {
00581     *m_streamOut << "<span style=\"";
00582     *m_streamOut << textFormatToCss(formatOrigin.text,format.text,false);
00583     *m_streamOut << "\">"; // close span opening tag
00584 
00585     if ( 1==format.text.verticalAlignment )
00586     {
00587         *m_streamOut << "<sub>"; //Subscript
00588     }
00589     else if ( 2==format.text.verticalAlignment )
00590     {
00591         *m_streamOut << "<sup>"; //Superscript
00592     }
00593 }
00594 
00595 void HtmlCssWorker::closeSpan(const FormatData& formatOrigin, const FormatData& format)
00596 {
00597     if ( 2==format.text.verticalAlignment )
00598     {
00599         *m_streamOut << "</sup>"; //Superscript
00600     }
00601     else if ( 1==format.text.verticalAlignment )
00602     {
00603         *m_streamOut << "</sub>"; //Subscript
00604     }
00605 
00606     *m_streamOut << "</span>";
00607 }
00608 
00609 bool HtmlCssWorker::doFullPaperFormat(const int format,
00610             const double width, const double height, const int orientation)
00611 {
00612     QString strWidth, strHeight, strUnits;
00613     KWEFUtil::GetNativePaperFormat(format, strWidth, strHeight, strUnits);
00614 
00615     if ((strWidth.isEmpty())||(strHeight.isEmpty())||(strUnits.isEmpty()))
00616     {
00617         // page format is unknown, so we need the size information
00618         strUnits="pt";
00619         strWidth=QString::number(width);
00620         strHeight=QString::number(height);
00621     }
00622     if (orientation==1)
00623     {
00624         // Landscape, so we must swap the sizes
00625         QString strTemp(strWidth);
00626         strWidth=strHeight;
00627         strHeight=strTemp;
00628     }
00629 
00630     m_strPageSize="size: ";
00631     m_strPageSize+=strWidth;
00632     m_strPageSize+=strUnits;
00633     m_strPageSize+=" ";
00634     m_strPageSize+=strHeight;
00635     m_strPageSize+=strUnits;
00636     m_strPageSize+=";";
00637     return true;
00638 }
00639 
00640 bool HtmlCssWorker::doFullPaperBorders (const double top, const double left,
00641     const double bottom, const double right)
00642 {
00643     m_strPaperBorders="  margin-top: ";
00644     m_strPaperBorders+=QString::number(top);
00645     m_strPaperBorders+="pt;\n";
00646     m_strPaperBorders+="  margin-left: ";
00647     m_strPaperBorders+=QString::number(left);
00648     m_strPaperBorders+="pt;\n";
00649     m_strPaperBorders+="  margin-bottom: ";
00650     m_strPaperBorders+=QString::number(bottom);
00651     m_strPaperBorders+="pt;\n";
00652     m_strPaperBorders+="  margin-right: ";
00653     m_strPaperBorders+=QString::number(right);
00654     m_strPaperBorders+="pt;\n";
00655 
00656     return true;
00657 }
00658 
00659 bool HtmlCssWorker::doOpenStyles(void)
00660 {
00661     *m_streamOut << "<style type=\"text/css\">\n";
00662     if (!isXML())
00663     {
00664         // Put the style under comment to increase the compatibility with old browsers
00665         // However in XHTML 1.0, you cannot put the style definition into HTML comments
00666         *m_streamOut << "<!--\n";
00667     }
00668 
00669     // Say who we are (with the CVS revision number)
00670     const QString strVersion("$Revision: 483471 $");
00671     // Eliminate the dollar signs
00672     //  (We don't want that the version number changes if the HTML file is itself put in a CVS storage.)
00673     *m_streamOut << "/* KWORD_CSS_EXPORT ="
00674               << strVersion.mid(10).remove('$')
00675               << "*/\n";
00676 
00677     // TODO: does KWord gives a paper colour?
00678     *m_streamOut << "BODY\n{\n  background-color: #FFFFFF\n}\n";
00679 
00680     return true;
00681 }
00682 
00683 bool HtmlCssWorker::doFullDefineStyle(LayoutData& layout)
00684 {
00685     //Register style in the style map
00686     m_styleMap[layout.styleName]=layout;
00687 
00688     // We do not limit (anymore) any style to <h1> ... <h6>, because
00689     //   the style could be forced on <p> by the layout.
00690 
00691     *m_streamOut << "." << escapeCssIdentifier(layout.styleName);
00692     *m_streamOut << "\n{\n  " << layoutToCss(layout,layout,true) << "\n}\n";
00693 
00694     return true;
00695 }
00696 
00697 bool HtmlCssWorker::doCloseStyles(void)
00698 {
00699     if (!m_strPageSize.isEmpty())
00700     {
00701         *m_streamOut << "@page\n{\n  ";
00702         *m_streamOut << m_strPageSize;
00703         *m_streamOut << "\n";
00704         *m_streamOut << m_strPaperBorders; // ends with a LF
00705         *m_streamOut << "}\n";
00706     }
00707 
00708     if (!isXML())
00709     {
00710         // Put the style under comment to increase the compatibility with old browsers
00711         // However in XHTML 1.0, you cannot put the style definition into HTML comments
00712         *m_streamOut << "-->\n";
00713     }
00714     *m_streamOut << "</style>\n";
00715 
00716     return true;
00717 }
00718 
KDE Home | KDE Accessibility Home | Description of Access Keys