static_context.h
Go to the documentation of this file.
00001 /*
00002  * Copyright 2006-2008 The FLWOR Foundation.
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  * http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 #ifndef XQP_STATIC_CONTEXT_API_H
00017 #define XQP_STATIC_CONTEXT_API_H
00018 
00019 #include <zorba/config.h>
00020 #include <zorba/api_shared_types.h>
00021 #include <zorba/zorba_string.h>
00022 #include <zorba/typeident.h>
00023 #include <zorba/static_context_consts.h>
00024 #include <zorba/options.h>  // for compiler hints class
00025 #include <vector>
00026 #include <zorba/function.h>
00027 #include <zorba/annotation.h>
00028 #include <zorba/smart_ptr.h>
00029 #ifndef ZORBA_NO_FULL_TEXT
00030 #include <zorba/thesaurus.h>
00031 #endif /* ZORBA_NO_FULL_TEXT */
00032 
00033 namespace zorba {
00034 
00035   /** \brief Instances of the class StaticContext contain the information that is available
00036    *         at the time the query is compiled.
00037    *
00038    * This class contains the information that is defined in the %XQuery specification
00039    * (see http://www.w3.org/TR/xquery/#static_context).
00040    *
00041    * A StaticContext can be created by calling Zorba::createStaticContext and then be passed
00042    * to the Zorba::compileQuery or XQuery::compile functions.
00043    * If no static context has been passed to any of these functions, a default static context
00044    * is used. It can be accessed by calling XQuery::getStaticContext on a compiled XQuery object.
00045    *
00046    * Note: This class is reference counted. When writing multi-threaded clients,
00047    * it is the responibility of the client code to synchronize assignments to the
00048    * SmartPtr holding this object.
00049    */
00050 class ZORBA_DLL_PUBLIC StaticContext : public SmartObject
00051 {
00052  public:
00053   /** \brief Destructor
00054    */
00055   virtual ~StaticContext() {}
00056 
00057   /** \brief Loads the declarations and definitions of a given XQuery prolog into
00058    *         this static context.
00059    *
00060    * This function compiles the prolog passed as first parameter and loads
00061    * all declarations and definitions into this static context.
00062    *
00063    * The static context extended by this prolog can then be used for creating
00064    * a compiling a new query.
00065    *
00066    * A StaticException is raised if the prolog could not be compiled or
00067    * if the prolog does not contain valid declarations (e.g. duplicate declarations).
00068    */
00069   virtual void
00070   loadProlog(const String&, const Zorba_CompilerHints_t &hints) = 0;
00071   
00072   /** \brief Create a child static context, i.e. a context with the same information,
00073    *         of the given static context.
00074    *
00075    * A child static context carries the same context as it's parent but
00076    * can override any information.
00077    */
00078   virtual StaticContext_t
00079   createChildContext() const = 0;
00080 
00081   /** \brief Add a pair (prefix, URI) to the statically known namespaces that
00082    *         are available during query compilation.
00083    *
00084    *  See http://www.w3.org/TR/xquery/#static_context.
00085    *
00086    *  @param aPrefix the prefix String.
00087    *  @param aURI the URI String.
00088    *  @return true if the pair was added to the set of statically known namespaces,
00089    *          false otherwise.
00090    *  @throw ZorbaException if an error occures.
00091    */
00092   virtual bool
00093   addNamespace( const String& aPrefix, const String& aURI ) = 0;
00094 
00095   /** \brief Get the namespace URI for a given prefix.
00096    *
00097    * @param aPrefix the prefix for which to retrieve the namespace URI.
00098    * @return String the URI for the given prefix or an empty String if no URI
00099    *         could be found for the given prefix and an DiagnosticHandler has been
00100    *         registered.
00101    * @throw ZorbaException if an error occured (e.g. no URI could be found for the given prefix).
00102    */
00103   virtual String
00104   getNamespaceURIByPrefix( const String& aPrefix ) const = 0;
00105 
00106   /** \brief Set the default element and type namespace
00107    *         (see http://www.w3.org/TR/xquery/#static_context)
00108    *
00109    * @param aURI of the default element and type namespace URI.
00110    * @return true if the default element and type namespace URI has been set, false otherwise
00111    *         if an DiagnosticHandler has been registered.
00112    * @throw ZorbaException if an error occured.
00113    */
00114   virtual bool
00115   setDefaultElementAndTypeNamespace( const String& aURI ) = 0;
00116   
00117   /** \brief Get the default element and type namespace URI.
00118    *
00119    * @return String the URI for the default element and type namespace.
00120    * @throw ZorbaException if an error occured.
00121    */
00122   virtual String
00123   getDefaultElementAndTypeNamespace( ) const = 0;
00124   
00125   /** \brief Set the default functionnamespace
00126    *         (see http://www.w3.org/TR/xquery/#static_context)
00127    *
00128    * @param aURI of the default function namespace.
00129    * @return true if the default function namespace URI has been set, false otherwise
00130    *         if an DiagnosticHandler has been registered.
00131    * @throw ZorbaException if an error occured.
00132    */
00133   virtual bool
00134   setDefaultFunctionNamespace( const String& aURI ) = 0;
00135 
00136   /** \brief Get the default function namespace.
00137    *
00138    * @return String the URI of the default function namespace.
00139    *         DiagnosticHandler has been registered.
00140    * @throw ZorbaException if an error occured.
00141    */
00142   virtual String
00143   getDefaultFunctionNamespace( ) const = 0;
00144 
00145   /** \brief Adds a collation URI.
00146    *
00147    * The URI specifies the locale and collation strength of the collation that is added.
00148    * A valid collation URI must begin with %http://www.zorba-xquery.com/collations/.
00149    * This prefix is followed by a collation strength (i.e. PRIMARY, SECONDARY, TERTIARY,
00150    * QUATTERNARY, or IDENTICAL) followed by a '/'.
00151    * After the strength a lower-case two- or three-letter ISO-639 language code must follow.
00152    * The URI may end with an upper-case two-letter ISO-3166.
00153    * For example, %http://www.zorba-xquery.com/collations/PRIMARY/en/US
00154    * specifies an english language with US begin the country..
00155    *
00156    * Internally, ICU is used for comparing strings. For detailed description see
00157    * http://www.icu-project.org/apiref/icu4c/classCollator.html
00158    * and http://www.icu-project.org/apiref/icu4c/classLocale.html
00159    *
00160    * @param aURI the URI of the collation.
00161    * @throw ZorbaException if an error occured (e.g. the URI was not a valid
00162    *        collation URI).
00163    */
00164   virtual void
00165   addCollation( const String& aURI ) = 0;
00166 
00167   /** \brief Set the URI of the default collation.
00168    *         (see http://www.w3.org/TR/xquery/#static_context)
00169    *
00170    * @param aURI URI of the default collation.
00171    * @throw ZorbaException if an error occured (e.g., the URI does not
00172    *        identify a collation among the statically known collations.
00173    */
00174   virtual void
00175   setDefaultCollation( const String& aURI ) = 0;
00176 
00177   /** \brief Get the URI of the default collation
00178    *
00179    * @return String the URI of the default collation.
00180    */
00181   virtual String
00182   getDefaultCollation() const = 0;
00183 
00184   /** \brief Set the XQuery processing mode (version 1.0 or 3.0).
00185    *
00186    *
00187    * @param aMode the XQuery version.
00188    * @return true if the version was set, false otherwise.
00189    */
00190   virtual bool
00191   setXQueryVersion( xquery_version_t aMode ) = 0;
00192 
00193   /** \brief Get the XQuery processing mode (version 1.0 or 3.0).
00194    *
00195    *
00196    * @return xquery_version_t the XQuery version processing mode.
00197    */
00198   virtual xquery_version_t
00199   getXQueryVersion( ) const = 0;
00200 
00201   /** \brief Set the XPath 1.0 compatibility mode.
00202    *         (see http://www.w3.org/TR/xquery/#static_context)
00203    *
00204    * @param aMode the XPath 1.0 compatibility mode.
00205    * @return true if the mode was set, false otherwise.
00206    */
00207   virtual bool
00208   setXPath1_0CompatibMode( xpath1_0compatib_mode_t aMode ) = 0;
00209 
00210   /** \brief Get the XPath 1.0 compatibility mode.
00211    *         (see http://www.w3.org/TR/xquery/#static_context)
00212    *
00213    * @return xpath1_0compatib_mode_t the XPath 1.0 compatibility mode.
00214    */
00215   virtual xpath1_0compatib_mode_t
00216   getXPath1_0CompatibMode( ) const = 0;
00217 
00218   /** \brief Set the construction mode.
00219    *         (see http://www.w3.org/TR/xquery/#static_context)
00220    *
00221    * @param aMode the construction mode.
00222    * @return true if the mode was set, false otherwise.
00223    */
00224   virtual bool
00225   setConstructionMode( construction_mode_t aMode ) = 0;
00226 
00227   /** \brief Get the construction mode.
00228    *         (see http://www.w3.org/TR/xquery/#static_context)
00229    *
00230    * @return construction_mode_t the construction mode.
00231    */
00232   virtual construction_mode_t
00233   getConstructionMode( ) const = 0;
00234 
00235   /** \brief Set the ordering mode.
00236    *         (see http://www.w3.org/TR/xquery/#static_context)
00237    *
00238    * @param aMode the ordering mode.
00239    * @return true if the mode was set, false otherwise.
00240    */
00241   virtual bool
00242   setOrderingMode( ordering_mode_t aMode ) = 0;
00243 
00244   /** \brief Get the ordering mode.
00245    *         (see http://www.w3.org/TR/xquery/#static_context)
00246    *
00247    * @return ordering_mode_t the ordering mode.
00248    */
00249   virtual ordering_mode_t
00250   getOrderingMode( ) const = 0;
00251 
00252   /** \brief Set the default order for the empty sequence.
00253    *         (see http://www.w3.org/TR/xquery/#static_context)
00254    *
00255    * @param aMode the default order for the empty sequence.
00256    * @return true if the mode was set, false otherwise.
00257    */
00258   virtual bool
00259   setDefaultOrderForEmptySequences( order_empty_mode_t aMode ) = 0;
00260 
00261   /** \brief Get the default order for the empty sequence.
00262    *         (see http://www.w3.org/TR/xquery/#static_context)
00263    *
00264    * @return order_empty_mode_t the ordering mode.
00265    */
00266   virtual order_empty_mode_t
00267   getDefaultOrderForEmptySequences( ) const = 0;
00268 
00269   /** \brief Set the boundary space policy.
00270    *         (see http://www.w3.org/TR/xquery/#static_context)
00271    *
00272    * @param aMode the boundary space policy.
00273    * @return true if the mode was set, false otherwise.
00274    */
00275   virtual bool
00276   setBoundarySpacePolicy( boundary_space_mode_t aMode) = 0;
00277 
00278   /** \brief Get the boundary space policy.
00279    *         (see http://www.w3.org/TR/xquery/#static_context)
00280    *
00281    * @return boundary_space_mode_t the boundary space policy.
00282    */
00283   virtual boundary_space_mode_t
00284   getBoundarySpacePolicy( ) const = 0;
00285 
00286   /** \brief Set the copy namespace mode.
00287    *         (see http://www.w3.org/TR/xquery/#static_context)
00288    *
00289    * @param aPreserve the preserve mode.
00290    * @param aInherit the inherit mode.
00291    * @return true if the mode was set, false otherwise.
00292    */
00293   virtual bool
00294   setCopyNamespacesMode( preserve_mode_t aPreserve,
00295                          inherit_mode_t aInherit ) = 0;
00296 
00297   /** \brief Get the copy namespace mode.
00298    *         (see http://www.w3.org/TR/xquery/#static_context)
00299    *
00300    * @return aPreserve the preserve mode.
00301    * @return aInherit the inherit mode.
00302    */
00303   virtual void
00304   getCopyNamespacesMode( preserve_mode_t& aPreserve,
00305                          inherit_mode_t& aInherit ) const = 0;
00306 
00307   /** \brief Set the base URI.
00308    *         (see http://www.w3.org/TR/xquery/#static_context)
00309    *
00310    * @param aBaseURI the base URI as String.
00311    * @return true if the base URI has been set, false otherwise.
00312    */
00313   virtual bool
00314   setBaseURI( const String& aBaseURI ) = 0;
00315 
00316   /** \brief Get the base URI.
00317    *
00318    * @return String the base URI.
00319    */
00320   virtual String
00321   getBaseURI( ) const = 0;
00322 
00323   /** \brief Get the revalidation mode.
00324    *
00325    * @return the revalidation mode.
00326    */
00327   virtual validation_mode_t
00328   getRevalidationMode() const = 0;
00329 
00330   /** \brief Set the revalidation mode.
00331    *
00332    * @param aMode the revalidation mode.
00333    */
00334   virtual void
00335   setRevalidationMode(validation_mode_t aMode) = 0;
00336   
00337   /** \brief Register a module providing access to external functions.
00338    *
00339    * Register a module that provides access to external functions.
00340    * The caller keeps the ownership of the Module and the StatelessExternalFunction
00341    * objects passed to this function.
00342    *
00343    * @param aModule the module object
00344    * @return true if the module has been set, false otherwise.
00345    */
00346   virtual bool
00347   registerModule(ExternalModule* aModule) = 0;
00348 
00349   /**
00350    * \brief Register a URI Mapper which will transform a given URI
00351    * into several alternate potential URIs.
00352    *
00353    * QQQ doc
00354    */
00355   virtual void
00356   registerURIMapper(URIMapper* aMapper) = 0;
00357 
00358   /**
00359    * \brief Register a URL Resolver which will transform a given
00360    * URL into a Resource.
00361    *
00362    * QQQ doc
00363    */
00364   virtual void
00365   registerURLResolver(URLResolver* aResolver) = 0;
00366 
00367   /** \brief Set the type of a statically known document
00368    */
00369   virtual void
00370   setDocumentType(const String& aDocUri, TypeIdentifier_t type) = 0;
00371   
00372   /** \brief Get the type of a statically known document
00373    */
00374   virtual TypeIdentifier_t
00375   getDocumentType(const String& aDocUri) const = 0;
00376   
00377   /** \brief Set the type of a statically known collection
00378    */
00379   virtual void
00380   setCollectionType(const String& aCollectionUri, TypeIdentifier_t type) = 0;
00381 
00382   /** \brief Get the type of a statically known collection
00383    */
00384   virtual TypeIdentifier_t
00385   getCollectionType(const String& aCollectionUri) const = 0;
00386 
00387   /** \brief Check if a function with the given name and arity are registered in the context.
00388    */
00389   virtual bool
00390   containsFunction(const String& aFnNameUri, const String& aFnNameLocal, int arity) const = 0;
00391 
00392   virtual void
00393   findFunctions(const Item& aQName, std::vector<Function_t>& aFunctions) const = 0;
00394   
00395   virtual void
00396   disableFunction(const Function_t& aFunction) = 0;
00397 
00398   virtual void
00399   disableFunction(const Item& aQName, int arity) = 0;
00400 
00401   virtual void
00402   getFunctionAnnotations(const Item& aQName, int arity, std::vector<Annotation_t>& aAnnotations) const = 0;
00403 
00404   /** \brief Set the type of the context item.
00405    */
00406   virtual void
00407   setContextItemStaticType(TypeIdentifier_t type) = 0;
00408 
00409   /** \brief Fetch the type of the context item.
00410    */
00411   virtual TypeIdentifier_t
00412   getContextItemStaticType() const = 0;
00413 
00414   /** \brief Set the output stream that is used by the fn:trace function
00415    *
00416    * Sets the output stream that is used by the fn:trace function to the given output stream.
00417    * The default stream is std::cerr.
00418    *
00419    */
00420   virtual void
00421   setTraceStream(std::ostream&) = 0;
00422 
00423   /** \brief Resets the output stream that is used by the fn:trace function to std::cerr
00424    */
00425   virtual void
00426   resetTraceStream() = 0;
00427 
00428   /** \brief Get an option that was declared using the declare option syntax
00429    *
00430    * @param aQName The QName of the option to get.
00431    * @param aOptionValue The value of the option if found.
00432    * @return true if the option was found, false otherwise.
00433    */
00434   virtual bool
00435   getOption( const Item& aQName, String& aOptionValue) const = 0;
00436 
00437   /** \brief Declare an option (same as using declare option in XQuery)
00438    *
00439    * @param aQName The QName of the option to declare.
00440    * @param aOptionValue The value of the option to declare.
00441    */
00442   virtual void
00443   declareOption( const Item& aQName, const String& aOptionValue) = 0;
00444 
00445   /**
00446    * @brief Set the URI and library lookup paths (lists of filesystem
00447    * directories) for this static context. Note that calling this method
00448    * will override any values previously passed to \link setURIPath()
00449    * and \link setLibPath().
00450    * @deprecated Use \link setURIPath() and \link setLibPath().
00451    *
00452    * Convenience method which adds the listed directories to both the
00453    * URI path and Library path for this static context.
00454    */
00455   virtual void
00456   setModulePaths( const std::vector<String>& aModulePaths ) = 0;
00457 
00458   /**
00459    * @brief Return the union of the URI and library lookup paths (lists of
00460    * filesystem directories) for this static context. @deprecated Use \link
00461    * getURIPath() and \link getLibPath().
00462    * @deprecated Use \link getURIPath() and \link getLibPath().
00463    *
00464    * Returns any values set by \link setLibPath() and/or \link setURIPath()
00465    * on this static context.
00466    */
00467   virtual void
00468   getModulePaths( std::vector<String>& aModulePaths ) const = 0;
00469 
00470   /**
00471    * @brief Return the union of the URI and library lookup paths (lists of
00472    * filesystem directories) for this static context and all its parents.
00473    * @deprecated Use \link getFullURIPath() and \link getFullLibPath().
00474    */
00475   virtual void
00476   getFullModulePaths( std::vector<String>& aFullModulePaths ) const = 0;
00477 
00478   /** \brief Resolves the given URI against the value of the base-uri
00479    * property from the static context.
00480    *
00481    * @param aRelativeUri The relative URI to be resolved.
00482    */
00483   virtual String
00484   resolve(const String& aRelativeUri) const = 0;
00485 
00486   /** \brief Resolves the given relative URI against the absolute base URI.
00487    *
00488    * @param aRelativeUri The relative URI to be resolved.
00489    * @param aBaseUri The absolute URI against which the resolving is performed.
00490    */
00491   virtual String
00492   resolve(const String& aRelativeUri, const String& aBaseUri) const = 0;
00493 
00494   /** \brief Validates this Item.
00495    *  Note: works only on document and element nodes, otherwise returns false.
00496    * 
00497    * @param rootElement the root of the tree beeing validated
00498    * @param validatedResult the result of the validation
00499    * @param validationMode Validation mode: default value is validate_strict
00500    * @return true if validation is correct, false if validation is disabled, throws errors if validation fails
00501    * @throw ZorbaException if any validation error occured
00502    */
00503   virtual bool
00504   validate(
00505       const Item& rootElement,
00506       Item& validatedResult,
00507       validation_mode_t validationMode = validate_strict) const = 0;
00508 
00509   /** \brief Validates this Item while loading the schema for targetNamespace
00510    *  Note: works only on document or element nodes, otherwise returns false.
00511    *
00512    * @param rootElement the root of the tree beeing validated 
00513    * @param validatedResult the result of the validation
00514    * @param targetNamespace the expected namespace of root of the tree beeing validated ???
00515    * @param validationMode Validation mode: default value is validate_strict
00516    * @return true if validation is correct, false if validation is disabled, throws errors if validation fails
00517    * @throw ZorbaException if any validation error occured
00518    */
00519   virtual bool 
00520   validate(
00521       const Item& rootElement,
00522       Item& validatedResult, 
00523       const String& targetNamespace,
00524       validation_mode_t validationMode = validate_strict) const = 0;
00525   
00526   /** \brief Validates stringValue as XML simple content, i.e. the text value of attributes or 
00527    * text only element content.
00528    * 
00529    * @param stringValue the value to be validated
00530    * @param typeQName
00531    * @param resultList the result of the validation, a vector of atomic Items
00532    * @return true if validation is correct, false if validation is disabled, throws errors if validation fails
00533    * @throw ZorbaException if any validation error occured
00534    */
00535   virtual bool 
00536   validateSimpleContent(
00537       const String& stringValue,
00538       const Item& typeQName, 
00539       std::vector<Item>& resultList) const= 0;
00540 
00541   /** \brief Invokes the XQuery function with the given name and
00542    *  the given parameters.
00543    *
00544    *  Note that the function to be invoked needs to be declared in this static
00545    *  context. In order to declare a function in the static context, the
00546    *  loadProlog method of this class can be used.
00547    *
00548    *  Also note that if the function to be invoked is an updating function,
00549    *  its resulting pending update list is implicitly applied by this function.
00550    *
00551    * @param aQName the name of the function to be invoked
00552    * @param aArgs a vector of ItemSequences. One entry in the vector
00553    *        corresponds to one argument that is passed to the function.
00554    *
00555    * @return The result of the function that is invoked. If the function
00556    *   to be invoked is an updating function, the resulting item sequence
00557    *   is empty.
00558    */
00559   virtual ItemSequence_t
00560   invoke(const Item& aQName, const std::vector<ItemSequence_t>& aArgs) const = 0;
00561 
00562   /** \brief Returns a CollectionManager responsible for all collections
00563    * which are statically declared in this static context.
00564    *
00565    * The collection manager provides a set of functions for managing
00566    * collections and their contents.
00567    *
00568    * @return The collection manager responsible for managing
00569    *   collections of this context.
00570    *
00571    */
00572   virtual StaticCollectionManager*
00573   getStaticCollectionManager() const = 0;
00574   
00575   /**
00576    * @brief sets the audit event that will be populated during execution
00577    *
00578    * @param anEvent the audit event
00579    */
00580   virtual void
00581   setAuditEvent(audit::Event* anEvent) = 0;
00582 
00583   /**
00584    * @brief gets the audit event that is populated during execution
00585    *
00586    * @return the audit event
00587    */
00588   virtual audit::Event*
00589   getAuditEvent() const = 0;
00590 
00591 
00592   /** \brief Returns the QName of all external variables within the
00593    *        static context
00594    *
00595    * @param aVarsIter iterator to store the results.
00596    * @throw ZorbaException if an error occured.
00597    */
00598   virtual void
00599   getExternalVariables(Iterator_t& aVarsIter) const = 0;
00600 
00601   /**
00602    * @brief Set the URI lookup path (list of filesystem directories) for this
00603    * static context.
00604    *
00605    * Queries which resolve URIs (for instance, importing modules or schemas)
00606    * will look in these directories.
00607    */
00608   virtual void
00609   setURIPath(const std::vector<String>& aURIPath) = 0;
00610 
00611   /**
00612    * @brief Return the URI lookup path (list of filesystem directories) for
00613    * this static context.
00614    *
00615    * Returns any values set by \link setURIPath() on this static context.
00616    * To return the full URI lookup path for this static context and
00617    * all its parents (usually most useful), call \link getFullURIPath().
00618    */
00619   virtual void
00620   getURIPath(std::vector<String>& aURIPath) const = 0;
00621 
00622   /**
00623    * @brief Return the URI lookup path (list of filesystem directories) for
00624    * this static context and all its parents.
00625    */
00626   virtual void
00627   getFullURIPath(std::vector<String>& aURIPath) const = 0;
00628 
00629   /**
00630    * @brief Set the library lookup path (list of filesystem directories) for
00631    * this static context.
00632    *
00633    * Queries which import modules that have external function
00634    * implementations will look for the implementation of those functions
00635    * (shared libraries) in these directories.
00636    */
00637   virtual void
00638   setLibPath(const std::vector<String>& aLibPath) = 0;
00639 
00640   /**
00641    * @brief Return the URI lookup path (list of filesystem directories) for
00642    * this static context.
00643    *
00644    * Returns any values set by \link setLibPath() on this static context.
00645    * To return the full library lookup path for this static context and
00646    * all its parents (usually most useful), call \link getFullLibPath().
00647    */
00648   virtual void
00649   getLibPath(std::vector<String>& aLibPath) const = 0;
00650 
00651   /**
00652    * @brief Return the URI lookup path (list of filesystem directories) for
00653    * this static context and all its parents.
00654    */
00655   virtual void
00656   getFullLibPath(std::vector<String>& aLibPath) const = 0;
00657 };
00658 
00659 } /* namespace zorba */
00660 #endif
00661 /* vim:set et sw=2 ts=2: */
blog comments powered by Disqus