VirtualBox

Changeset 7387 in vbox


Ignore:
Timestamp:
Mar 9, 2008 11:54:02 PM (17 years ago)
Author:
vboxsync
Message:

Main/Settings: Perform conversion in a loop to allow for multi-step version to version updates (e.g. v1.0->v1.1->v1.2) which saves from patching all previous transformation rules when on every version change but still makes it possible to update from too old versions to the most recent one.

Location:
trunk/src/VBox/Main
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Main/VirtualBoxImplExtra.cpp

    r7341 r7387  
    7575 *
    7676 * The implementation normally checks for the "version" value of the
    77  * root key to determine if the conversion is necessary. The
    78  * implementation must return a string representing the old version
    79  * (before conversion) in the @c aOldVersion argument -- this string is
    80  * used by XmlTreeBackend::oldVersion() and must be non-NULL to indicate
    81  * that the conversion has been performed on the tree. The returned
    82  * string must be allocated using RTStrDup or such.
     77 * root key to determine if the conversion is necessary. When the
     78 * @a aOldVersion argument is not NULL, the implementation must return a
     79 * non-NULL non-empty string representing the old version (before
     80 * conversion) in it this string is used by XmlTreeBackend::oldVersion()
     81 * and must be non-NULL to indicate that the conversion has been
     82 * performed on the tree. The returned string must be allocated using
     83 * RTStrDup() or such.
     84 *
     85 * This method is called again after the successful transformation to
     86 * let the implementation retry the version check and request another
     87 * transformation if necessary. This may be used to perform multi-step
     88 * conversion like this: 1.1 => 1.2, 1.2 => 1.3 (instead of 1.1 => 1.3)
     89 * which saves from the need to update all previous conversion
     90 * templates to make each of them convert directly to the recent
     91 * version.
     92 *
     93 * @note Multi-step transformations are performed in a loop that exits
     94 *       only when this method returns @false. It's up to the
     95 *       implementation to detect cycling (repeated requests to convert
     96 *       from the same version) wrong version order, etc. and throw an
     97 *       EConversionCycle exception to break the loop without returning
     98 *       @false (which means the transformation succeeded).
    8399 *
    84100 * @param aRoot                 Root settings key.
    85  * @param aOldVersionString     Old version string (allocated by
    86  *                              RTStrDup or such).
     101 * @param aOldVersionString     Where to store old version string
     102 *                              pointer. May be NULL.
    87103 */
    88104bool VirtualBox::SettingsTreeHelper::
    89 needsConversion (const settings::Key &aRoot, char *&aOldVersion) const
     105needsConversion (const settings::Key &aRoot, char **aOldVersion) const
    90106{
    91107    if (strcmp (aRoot.name(), "VirtualBox") == 0)
     
    98114            {
    99115                /* version mismatch */
    100                 aOldVersion = RTStrDup (version);
     116                if (aOldVersion != NULL)
     117                    *aOldVersion = RTStrDup (version);
     118
    101119                return true;
    102120            }
  • trunk/src/VBox/Main/include/VirtualBoxImpl.h

    r7349 r7387  
    272272
    273273        // AutoConverter interface
    274         bool needsConversion (const settings::Key &aRoot, char *&aOldVersion) const;
     274        bool needsConversion (const settings::Key &aRoot, char **aOldVersion) const;
    275275        const char *templateUri() const;
    276276    };
  • trunk/src/VBox/Main/xml/Settings.cpp

    r7315 r7387  
    893893}
    894894
     895extern "C" xmlGenericErrorFunc xsltGenericError;
     896extern "C" void *xsltGenericErrorContext;
     897
    895898void XmlTreeBackend::rawRead (Input &aInput, const char *aSchema /* = NULL */,
    896899                              int aFlags /* = 0 */)
     
    905908     * mutex and b) requiring our API caller not to use libxml2 on some other
    906909     * thread (which is not practically possible). So, our API is not
    907      * thread-safe for now. */
     910     * thread-safe for now (note that there are more thread-unsafe assumptions
     911     * below like xsltGenericError which is also a libxslt limitation).*/
    908912    xmlExternalEntityLoader oldEntityLoader = xmlGetExternalEntityLoader();
    909913    sThat = this;
    910914    xmlSetExternalEntityLoader (ExternalEntityLoader);
    911915
    912     /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
    913      * remove text nodes that contain only blanks. This is important because
    914      * otherwise xmlSaveDoc() won't be able to do proper indentation on
    915      * output. */
    916 
    917     /* parse the stream */
    918     /* NOTE: new InputCtxt instance will be deleted when the stream is closed by
    919      * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */
    920     xmlDocPtr doc = xmlCtxtReadIO (m->ctxt,
    921                                    ReadCallback, CloseCallback,
    922                                    new Data::InputCtxt (&aInput, m->trappedErr),
    923                                    aInput.uri(), NULL,
    924                                    XML_PARSE_NOBLANKS);
    925     if (doc == NULL)
    926     {
    927         /* restore the previous entity resolver */
    928         xmlSetExternalEntityLoader (oldEntityLoader);
    929         sThat = NULL;
    930 
    931         /* look if there was a forwared exception from the lower level */
    932         if (m->trappedErr.get() != NULL)
    933             m->trappedErr->rethrow();
    934 
    935         throw XmlError (xmlCtxtGetLastError (m->ctxt));
    936     }
    937 
    938     char *oldVersion = NULL;
    939 
    940     /* perform automatic document transformation if necessary */
    941     if (m->autoConverter != NULL)
    942     {
    943         Key root = Key (new XmlKeyBackend (xmlDocGetRootElement (doc)));
    944         if (m->autoConverter->needsConversion (root, oldVersion))
     916    xmlDocPtr doc = NULL;
     917
     918    try
     919    {
     920        /* Note: when parsing we use XML_PARSE_NOBLANKS to instruct libxml2 to
     921         * remove text nodes that contain only blanks. This is important because
     922         * otherwise xmlSaveDoc() won't be able to do proper indentation on
     923         * output. */
     924
     925        /* parse the stream */
     926        /* NOTE: new InputCtxt instance will be deleted when the stream is closed by
     927         * the libxml2 API (e.g. when calling xmlFreeParserCtxt()) */
     928        doc = xmlCtxtReadIO (m->ctxt,
     929                             ReadCallback, CloseCallback,
     930                             new Data::InputCtxt (&aInput, m->trappedErr),
     931                             aInput.uri(), NULL,
     932                             XML_PARSE_NOBLANKS);
     933        if (doc == NULL)
     934        {
     935            /* look if there was a forwared exception from the lower level */
     936            if (m->trappedErr.get() != NULL)
     937                m->trappedErr->rethrow();
     938
     939            throw XmlError (xmlCtxtGetLastError (m->ctxt));
     940        }
     941
     942        char *oldVersion = NULL;
     943
     944        /* perform automatic document transformation if necessary */
     945        if (m->autoConverter != NULL &&
     946            m->autoConverter->
     947                needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))),
     948                                 &oldVersion))
    945949        {
    946950            xmlDocPtr xsltDoc = NULL;
    947951            xsltStylesheetPtr xslt = NULL;
    948             xsltTransformContextPtr tranCtxt = NULL;
    949952            char *errorStr = NULL;
     953
     954            xmlGenericErrorFunc oldXsltGenericError = xsltGenericError;
     955            void *oldXsltGenericErrorContext = xsltGenericErrorContext;
    950956
    951957            try
     
    975981                }
    976982
     983                /* setup stylesheet compilation and transformation error
     984                 * reporting. Note that we could create a new transform context
     985                 * for doing xsltApplyStylesheetUser and use
     986                 * xsltSetTransformErrorFunc() on it to set a dedicated error
     987                 * handler but as long as we already do several non-thread-safe
     988                 * hacks, this is not really important. */
     989
     990                xsltGenericError = ValidityErrorCallback;
     991                xsltGenericErrorContext = &errorStr;
     992
    977993                xslt = xsltParseStylesheetDoc (xsltDoc);
    978994                if (xslt == NULL)
     995                {
     996                    if (errorStr != NULL)
     997                        throw LogicError (errorStr);
     998                    /* errorStr is freed in catch(...) below */
     999
    9791000                    throw LogicError (RT_SRC_POS);
    980 
    981                 /* setup transformation error reporting */
    982                 tranCtxt = xsltNewTransformContext (xslt, xsltDoc);
    983                 if (tranCtxt == NULL)
    984                     throw LogicError (RT_SRC_POS);
    985                 xsltSetTransformErrorFunc (tranCtxt, &errorStr, ValidityErrorCallback);
    986 
    987                 xmlDocPtr newDoc = xsltApplyStylesheetUser (xslt, doc, NULL,
    988                                                             NULL, NULL, tranCtxt);
    989                 if (newDoc == NULL)
    990                     throw LogicError (RT_SRC_POS);
    991 
    992                 if (errorStr != NULL)
     1001                }
     1002
     1003                /* repeat transformations until autoConverter is satisfied */
     1004                do
    9931005                {
    994                     xmlFreeDoc (newDoc);
    995                     throw Error (errorStr);
    996                     /* errorStr is freed in catch(...) below */
     1006                    xmlDocPtr newDoc = xsltApplyStylesheet (xslt, doc, NULL);
     1007                    if (newDoc == NULL)
     1008                        throw LogicError (RT_SRC_POS);
     1009
     1010                    if (errorStr != NULL)
     1011                    {
     1012                        xmlFreeDoc (newDoc);
     1013                        throw Error (errorStr);
     1014                        /* errorStr is freed in catch(...) below */
     1015                    }
     1016
     1017                    /* replace the old document on success */
     1018                    xmlFreeDoc (doc);
     1019                    doc = newDoc;
    9971020                }
    998 
    999                 /* replace the old document on success */
    1000                 xmlFreeDoc (doc);
    1001                 doc = newDoc;
    1002 
    1003                 xsltFreeTransformContext (tranCtxt);
     1021                while (m->autoConverter->
     1022                       needsConversion (Key (new XmlKeyBackend (xmlDocGetRootElement (doc))),
     1023                                        NULL));
     1024
     1025                RTStrFree (errorStr);
    10041026
    10051027                /* NOTE: xsltFreeStylesheet() also fress the document
    10061028                 * passed to xsltParseStylesheetDoc(). */
    10071029                xsltFreeStylesheet (xslt);
     1030
     1031                /* restore the previous generic error func */
     1032                xsltGenericError = oldXsltGenericError;
     1033                xsltGenericErrorContext = oldXsltGenericErrorContext;
    10081034            }
    10091035            catch (...)
    10101036            {
    1011                 /* restore the previous entity resolver */
    1012                 xmlSetExternalEntityLoader (oldEntityLoader);
    1013                 sThat = NULL;
    1014 
    10151037                RTStrFree (errorStr);
    1016 
    1017                 if (tranCtxt != NULL)
    1018                     xsltFreeTransformContext (tranCtxt);
    10191038
    10201039                /* NOTE: xsltFreeStylesheet() also fress the document
     
    10251044                    xmlFreeDoc (xsltDoc);
    10261045
     1046                /* restore the previous generic error func */
     1047                xsltGenericError = oldXsltGenericError;
     1048                xsltGenericErrorContext = oldXsltGenericErrorContext;
     1049
    10271050                RTStrFree (oldVersion);
    10281051
     
    10301053            }
    10311054        }
    1032     }
    1033 
    1034     /* validate the document */
    1035     if (aSchema != NULL)
    1036     {
    1037         xmlSchemaParserCtxtPtr schemaCtxt = NULL;
    1038         xmlSchemaPtr schema = NULL;
    1039         xmlSchemaValidCtxtPtr validCtxt = NULL;
    1040         char *errorStr = NULL;
    1041 
    1042         try
     1055
     1056        /* validate the document */
     1057        if (aSchema != NULL)
    10431058        {
    1044             bool valid = false;
    1045 
    1046             schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
    1047             if (schemaCtxt == NULL)
    1048                 throw LogicError (RT_SRC_POS);
    1049 
    1050             /* set our error handlers */
    1051             xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
    1052                                       ValidityWarningCallback, &errorStr);
    1053             xmlSchemaSetParserStructuredErrors (schemaCtxt,
    1054                                                 StructuredErrorCallback,
    1055                                                 &errorStr);
    1056             /* load schema */
    1057             schema = xmlSchemaParse (schemaCtxt);
    1058             if (schema != NULL)
     1059            xmlSchemaParserCtxtPtr schemaCtxt = NULL;
     1060            xmlSchemaPtr schema = NULL;
     1061            xmlSchemaValidCtxtPtr validCtxt = NULL;
     1062            char *errorStr = NULL;
     1063
     1064            try
    10591065            {
    1060                 validCtxt = xmlSchemaNewValidCtxt (schema);
    1061                 if (validCtxt == NULL)
     1066                bool valid = false;
     1067
     1068                schemaCtxt = xmlSchemaNewParserCtxt (aSchema);
     1069                if (schemaCtxt == NULL)
    10621070                    throw LogicError (RT_SRC_POS);
    10631071
    1064                 /* instruct to create default attribute's values in the document */
    1065                 if (aFlags & Read_AddDefaults)
    1066                     xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
    1067 
    10681072                /* set our error handlers */
    1069                 xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
    1070                                          ValidityWarningCallback, &errorStr);
    1071 
    1072                 /* finally, validate */
    1073                 valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
     1073                xmlSchemaSetParserErrors (schemaCtxt, ValidityErrorCallback,
     1074                                          ValidityWarningCallback, &errorStr);
     1075                xmlSchemaSetParserStructuredErrors (schemaCtxt,
     1076                                                    StructuredErrorCallback,
     1077                                                    &errorStr);
     1078                /* load schema */
     1079                schema = xmlSchemaParse (schemaCtxt);
     1080                if (schema != NULL)
     1081                {
     1082                    validCtxt = xmlSchemaNewValidCtxt (schema);
     1083                    if (validCtxt == NULL)
     1084                        throw LogicError (RT_SRC_POS);
     1085
     1086                    /* instruct to create default attribute's values in the document */
     1087                    if (aFlags & Read_AddDefaults)
     1088                        xmlSchemaSetValidOptions (validCtxt, XML_SCHEMA_VAL_VC_I_CREATE);
     1089
     1090                    /* set our error handlers */
     1091                    xmlSchemaSetValidErrors (validCtxt, ValidityErrorCallback,
     1092                                             ValidityWarningCallback, &errorStr);
     1093
     1094                    /* finally, validate */
     1095                    valid = xmlSchemaValidateDoc (validCtxt, doc) == 0;
     1096                }
     1097
     1098                if (!valid)
     1099                {
     1100                    /* look if there was a forwared exception from the lower level */
     1101                    if (m->trappedErr.get() != NULL)
     1102                        m->trappedErr->rethrow();
     1103
     1104                    if (errorStr == NULL)
     1105                        throw LogicError (RT_SRC_POS);
     1106
     1107                    throw Error (errorStr);
     1108                    /* errorStr is freed in catch(...) below */
     1109                }
     1110
     1111                RTStrFree (errorStr);
     1112
     1113                xmlSchemaFreeValidCtxt (validCtxt);
     1114                xmlSchemaFree (schema);
     1115                xmlSchemaFreeParserCtxt (schemaCtxt);
    10741116            }
    1075 
    1076             if (!valid)
     1117            catch (...)
    10771118            {
    1078                 /* look if there was a forwared exception from the lower level */
    1079                 if (m->trappedErr.get() != NULL)
    1080                     m->trappedErr->rethrow();
    1081 
    1082                 if (errorStr == NULL)
    1083                     throw LogicError (RT_SRC_POS);
    1084 
    1085                 throw Error (errorStr);
    1086                 /* errorStr is freed in catch(...) below */
     1119                RTStrFree (errorStr);
     1120
     1121                if (validCtxt)
     1122                    xmlSchemaFreeValidCtxt (validCtxt);
     1123                if (schema)
     1124                    xmlSchemaFree (schema);
     1125                if (schemaCtxt)
     1126                    xmlSchemaFreeParserCtxt (schemaCtxt);
     1127
     1128                RTStrFree (oldVersion);
     1129
     1130                throw;
    10871131            }
    1088 
    1089             RTStrFree (errorStr);
    1090 
    1091             xmlSchemaFreeValidCtxt (validCtxt);
    1092             xmlSchemaFree (schema);
    1093             xmlSchemaFreeParserCtxt (schemaCtxt);
    10941132        }
    1095         catch (...)
    1096         {
    1097             /* restore the previous entity resolver */
    1098             xmlSetExternalEntityLoader (oldEntityLoader);
    1099             sThat = NULL;
    1100 
    1101             RTStrFree (errorStr);
    1102 
    1103             if (validCtxt)
    1104                 xmlSchemaFreeValidCtxt (validCtxt);
    1105             if (schema)
    1106                 xmlSchemaFree (schema);
    1107             if (schemaCtxt)
    1108                 xmlSchemaFreeParserCtxt (schemaCtxt);
    1109 
    1110             RTStrFree (oldVersion);
    1111 
    1112             throw;
    1113         }
    1114     }
    1115 
    1116     /* restore the previous entity resolver */
    1117     xmlSetExternalEntityLoader (oldEntityLoader);
    1118     sThat = NULL;
    1119 
    1120     /* reset the previous tree on success */
    1121     reset();
    1122 
    1123     m->doc = doc;
    1124     /* assign the root key */
    1125     m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
    1126 
    1127     /* memorize the old version string also used as a flag that
    1128      * the conversion has been performed (transfers ownership) */
    1129     m->oldVersion = oldVersion;
     1133
     1134        /* reset the previous tree on success */
     1135        reset();
     1136
     1137        m->doc = doc;
     1138        /* assign the root key */
     1139        m->root = Key (new XmlKeyBackend (xmlDocGetRootElement (m->doc)));
     1140
     1141        /* memorize the old version string also used as a flag that
     1142         * the conversion has been performed (transfers ownership) */
     1143        m->oldVersion = oldVersion;
     1144
     1145        /* restore the previous entity resolver */
     1146        xmlSetExternalEntityLoader (oldEntityLoader);
     1147        sThat = NULL;
     1148    }
     1149    catch (...)
     1150    {
     1151        if (doc != NULL)
     1152            xmlFreeDoc (doc);
     1153
     1154        /* restore the previous entity resolver */
     1155        xmlSetExternalEntityLoader (oldEntityLoader);
     1156        sThat = NULL;
     1157
     1158        throw;
     1159    }
    11301160}
    11311161
  • trunk/src/VBox/Main/xml/SettingsConverter.xsl

    r7341 r7387  
    5151 *  Forbid non-VirtualBox root nodes
    5252-->
    53 
    5453<xsl:template match="/*">
    5554  <xsl:message terminate="yes">
     
    6160 *  Forbid all unsupported VirtualBox settings versions
    6261-->
    63 
    6462<xsl:template match="/vb:VirtualBox">
    6563  <xsl:if test="@version=concat($recentVer,'-',$curVerPlat)">
     
    7674
    7775<!--
    78  *  Accept supported settings versions (source setting filess to convert)
     76 * Accept supported settings versions (source setting files we can convert)
     77 *
     78 * Note that in order to simplify conversion from versions prior to the previous
     79 * one, we support multi-step conversion like this: step 1: 1.0 => 1.1,
     80 * step 2: 1.1 => 1.2, where 1.2 is the most recent version. If you want to use
     81 * such multi-step mode, you need to ensure that only 1.0 => 1.1 is possible, by
     82 * using the 'mode=1.1' attribute on both 'apply-templates' within the starting
     83 * '/vb:VirtualBox[1.0]' template and within all templates that this
     84 * 'apply-templates' should apply.
     85 *
     86 * If no 'mode' attribute is used as described above, then a direct conversion
     87 * (1.0 => 1.2 in the above example) will happen when version 1.0 of the settings
     88 * files is detected. Note that the direct conversion from pre-previous versions
     89 * will require to patch their conversion templates so that they include all
     90 * modifications from all newer versions, which is error-prone. It's better to
     91 * use the milt-step mode.
    7992-->
    8093
    8194<!-- @todo temporary -->
    82 <xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='1.999']">
     95<xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='0.1']">
     96  <xsl:copy>
     97    <xsl:attribute name="version"><xsl:value-of select="concat('0.2','-',$curVerPlat)"/></xsl:attribute>
     98    <xsl:apply-templates select="node()" mode="v0.2"/>
     99  </xsl:copy>
     100</xsl:template>
     101<xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='0.2']">
     102  <xsl:copy>
     103    <xsl:attribute name="version"><xsl:value-of select="concat('0.3','-',$curVerPlat)"/></xsl:attribute>
     104    <xsl:apply-templates select="node()" mode="v0.3"/>
     105  </xsl:copy>
     106</xsl:template>
     107
     108<xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='0.1']/vb:Machine"
     109              mode="v0.2">
     110  <Machine>
     111    0.2
     112  </Machine>
     113</xsl:template>
     114
     115<xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='0.2']/vb:Machine"
     116              mode="v0.3">
     117  <Machine>
     118    0.3
     119  </Machine>
     120</xsl:template>
     121
     122<!--
     123 *  all non-root elements that are not explicitly matched are copied as is
     124-->
     125<xsl:template match="@*|node()[../..]" mode="v0.2">
     126  <xsl:copy>
     127    <xsl:apply-templates select="@*|node()[../..]" mode="v0.2"/>
     128  </xsl:copy>
     129</xsl:template>
     130
     131<!--
     132 *  all non-root elements that are not explicitly matched are copied as is
     133-->
     134<xsl:template match="@*|node()[../..]" mode="v0.3">
     135  <xsl:copy>
     136    <xsl:apply-templates select="@*|node()[../..]" mode="v0.3"/>
     137  </xsl:copy>
     138</xsl:template>
     139
     140<!-- @todo do 1.1 => 1.2 => current? -->
     141<!--xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='1.1']">
    83142  <xsl:copy>
    84143    <xsl:attribute name="version"><xsl:value-of select="concat($recentVer,'-',$curVerPlat)"/></xsl:attribute>
    85144    <xsl:apply-templates select="node()"/>
    86145  </xsl:copy>
    87 </xsl:template>
    88 
    89 <xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='1.1']">
     146</xsl:template-->
     147
     148  <!--
     149<xsl:template match="/vb:VirtualBox[substring-before(@version,'-')='1.2"]">
    90150  <xsl:copy>
    91151    <xsl:attribute name="version"><xsl:value-of select="concat($recentVer,'-',$curVerPlat)"/></xsl:attribute>
     
    93153  </xsl:copy>
    94154</xsl:template>
    95 
    96 <!--
    97  * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    98  *  Individual conversions
    99  * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    100 -->
    101 
    102 <!--
    103  *  all non-root elements that are not explicitly matched are copied as is
    104 -->
    105 <xsl:template match="@*|node()[../..]">
    106   <xsl:copy>
    107     <xsl:apply-templates select="@*|node()[../..]"/>
    108   </xsl:copy>
    109 </xsl:template>
     155  -->
     156
     157<!--
     158 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     159 *  1.1 => 1.2 ???
     160 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     161-->
    110162
    111163<!--
     
    167219</xsl:template>
    168220
     221<!--
     222 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     223 *  1.2 => current
     224 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     225-->
     226
    169227</xsl:stylesheet>
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette