source: GTP/trunk/Lib/Vis/Preprocessing/src/X3dParser.cpp @ 660

Revision 660, 19.7 KB checked in by mattausch, 19 years ago (diff)

adding function for testing purpose

RevLine 
[170]1// ---------------------------------------------------------------------------
2//  Includes for all the program files to see
3// ---------------------------------------------------------------------------
4#include <string.h>
5#include <stdlib.h>
6#include <iostream>
7using namespace std;
8#include <xercesc/util/PlatformUtils.hpp>
[162]9
[170]10// ---------------------------------------------------------------------------
11//  Includes
12// ---------------------------------------------------------------------------
13#include <xercesc/framework/StdInInputSource.hpp>
14#include <xercesc/parsers/SAXParser.hpp>
15#include <xercesc/util/OutOfMemoryException.hpp>
16
17// ---------------------------------------------------------------------------
18//  Includes
19// ---------------------------------------------------------------------------
20#include <xercesc/sax/AttributeList.hpp>
21#include <xercesc/sax/SAXParseException.hpp>
22#include <xercesc/sax/SAXException.hpp>
23
[162]24#include "X3dParser.h"
25
[182]26#include "X3dParserXerces.h"
[170]27#include "Mesh.h"
28#include "SceneGraph.h"
[261]29#include "Triangle3.h"
[439]30#include "ViewCellsManager.h"
[162]31
[170]32// ---------------------------------------------------------------------------
33//  Local data
34//
35//  doNamespaces
36//      Indicates whether namespace processing should be enabled or not.
37//      The default is no, but -n overrides that.
38//
39//  doSchema
40//      Indicates whether schema processing should be enabled or not.
41//      The default is no, but -s overrides that.
42//
43//  schemaFullChecking
44//      Indicates whether full schema constraint checking should be enabled or not.
45//      The default is no, but -s overrides that.
46//
47//  valScheme
48//      Indicates what validation scheme to use. It defaults to 'auto', but
49//      can be set via the -v= command.
50// ---------------------------------------------------------------------------
51static bool     doNamespaces       = false;
52static bool     doSchema           = false;
53static bool     schemaFullChecking = false;
54static SAXParser::ValSchemes    valScheme       = SAXParser::Val_Auto;
55
56
57
58
59
60// ---------------------------------------------------------------------------
61//  StdInParseHandlers: Constructors and Destructor
62// ---------------------------------------------------------------------------
[657]63X3dParseHandlers::X3dParseHandlers(SceneGraphNode *root, const bool loadPolygonsAsMeshes):
[176]64  mElementCount(0)
65  , mAttrCount(0)
66  , mCharacterCount(0)
67  , mSpaceCount(0)
[657]68  , mLoadPolygonsAsMeshes(loadPolygonsAsMeshes)
[162]69{
[176]70  mCurrentNode = root;
[162]71}
72
[170]73X3dParseHandlers::~X3dParseHandlers()
74{
75}
[162]76
[170]77
78// ---------------------------------------------------------------------------
79//  StdInParseHandlers: Implementation of the SAX DocumentHandler interface
80// ---------------------------------------------------------------------------
81void X3dParseHandlers::endElement(const XMLCh* const name)
[162]82{
[170]83  StrX lname(name);
[176]84  string element(lname.LocalForm());
[170]85  if (element == "Shape")
86    EndShape();
87}
[162]88
[170]89void
90X3dParseHandlers::EndShape()
91{
[658]92        if (mLoadPolygonsAsMeshes)
93        {
94                vector<VertexIndexContainer>::const_iterator it, it_end = mCurrentVertexIndices.end();
95
96                for (it = mCurrentVertexIndices.begin(); it != it_end; ++ it)
97                {
98                        // only one face per mesh
99                        Mesh *mesh = new Mesh();
100
[660]101                        VertexIndexContainer vc;
102
[658]103                        // add vertices
104                        for (int i = 0; i < (int)(*it).size(); ++ i)
105                        {
106                                mesh->mVertices.push_back(mCurrentVertices[(*it)[i]]);
[660]107                                vc.push_back(i);
[658]108                        }
109                       
[660]110                        mesh->mFaces.push_back(new Face(vc));
[658]111                        mesh->Preprocess();
112                        // make an instance of this mesh
113                        MeshInstance *mi = new MeshInstance(mesh);
114                        mCurrentNode->mGeometry.push_back(mi);
115
116                }
117
118                delete mCurrentMesh;
119                mCurrentVertices.clear();
120                mCurrentVertexIndices.clear();
121        }
122        else
123        {
124 
125                if (mCurrentMesh->mFaces.size())
126                {
127                        mCurrentMesh->Preprocess();
128                        // make an instance of this mesh
129                        MeshInstance *mi = new MeshInstance(mCurrentMesh);
130                        mCurrentNode->mGeometry.push_back(mi);
131                        // set the object id to a unique value
132                        //mi->SetId(mCurrentObjectId ++);
133                }
134                else
135                {
136                        cout<<"X";
137                        delete mCurrentMesh;
138                }
139                mCurrentMesh = NULL;
140        }
[170]141}
142void
143X3dParseHandlers::StartIndexedFaceSet(
[439]144                                                                          AttributeList&  attributes)
[170]145{
146  int len = attributes.getLength();
147  int i;
148  VertexIndexContainer vertices;
[162]149 
[170]150  for (i=0; i < len; i++) {
[176]151    string attrName(StrX(attributes.getName(i)).LocalForm());
[170]152    if (attrName == "coordIndex") {
153      StrX attrValue(attributes.getValue(i));
154      // handle coordIndex
155      vertices.clear();
[176]156      const char *ptr = attrValue.LocalForm();
[170]157      char *endptr;
158      while(1) {
159        int index = strtol(ptr, &endptr, 10);
160        if (ptr == endptr || index == -1) {
161          if (vertices.size() > 2) {
[657]162                  Face *face = new Face(vertices);
163
[658]164                   if (!mLoadPolygonsAsMeshes)
165                   {
166                           mCurrentMesh->mFaces.push_back(face);
167                   }
168                   else
169                   // every polygon is a mesh
170                   {
171                           mCurrentVertexIndices.push_back(vertices);
172                   }
173          }
[657]174
[170]175          vertices.clear();
176          if (ptr == endptr)
177            break;
178        } else {
179          vertices.push_back(index);
180        }
181        ptr = endptr;
[508]182          }
[170]183    }
[162]184  }
185}
186
[170]187void
188X3dParseHandlers::StartMaterial(
189                                AttributeList&  attributes)
[162]190{
[170]191  int len = attributes.getLength();
192  int i;
[176]193  if (!mCurrentMesh->mMaterial)
194    mCurrentMesh->mMaterial = new Material;
[170]195  for (i=0; i < len; i++) {
[176]196    string attrName(StrX(attributes.getName(i)).LocalForm());
[170]197    StrX attrValue(attributes.getValue(i));
[176]198    const char *ptr = attrValue.LocalForm();
[170]199    if (attrName == "diffuseColor") {
200      float r, g, b;
201      if (sscanf(ptr, "%f %f %f", &r, &g, &b) == 3)
[176]202        mCurrentMesh->mMaterial->mDiffuseColor = RgbColor(r, g, b);
[170]203    }
204  }
[162]205}
206
[170]207void
208X3dParseHandlers::StartCoordinate(
209                                  AttributeList&  attributes)
[162]210{
[657]211        int len = attributes.getLength();
212       
213        int i;
214        VertexContainer vertices;
215       
216        for (i=0; i < len; i++)
217        {
218                string attrName(StrX(attributes.getName(i)).LocalForm());
219         
220                if (attrName == "point")
221                {
222                        StrX attrValue(attributes.getValue(i));
223                 
224
225                        const char *ptr = attrValue.LocalForm();
226                        char *endptr;
227
228
229                        while(1)
230                        {
231                                float x = (float)strtod(ptr, &endptr);
232               
233                                if (ptr == endptr)
234                                  break;
235                         
236                                ptr = endptr;
237                               
238                                float y = (float)strtod(ptr, &endptr);
239                         
240                                if (ptr == endptr)
241                                        break;
242
243                                ptr = endptr;
244                         
245                                float z = (float)strtod(ptr, &endptr);
246                                if (ptr == endptr)
247                                        break;
248                         
249                                ptr = endptr;
250                         
251                                if (*ptr == ',')
252                                        ptr ++;
253
254                                Vector3 v(x, y, z);
255                                vertices.push_back(v);
256                        }
[658]257                        if (mLoadPolygonsAsMeshes)
258                        {
259                                mCurrentVertices = vertices;
260                        }
261                        else
262                        {
263                                mCurrentMesh->mVertices = vertices;
264                        }
[657]265                }
266        }
[162]267}
268
[170]269
270void
271X3dParseHandlers::startElement(const XMLCh* const name,
[439]272                                                           AttributeList&  attributes)
[162]273{
[170]274  StrX lname(name);
[176]275  string element(lname.LocalForm());
[170]276 
277  if (element == "IndexedFaceSet") {
278    // create a new mesh node in the scene graph
279    StartIndexedFaceSet(attributes);
280  }
281
282  if (element == "Shape") {
283    cout<<"+";
[658]284        mCurrentMesh = new Mesh;
[170]285  }
286 
287  if (element == "Coordinate") {
[176]288    if (mCurrentMesh)
[170]289      StartCoordinate(attributes);
290  }
291 
292  if (element == "Material") {
293    StartMaterial(attributes);
294  }
295
[176]296  mElementCount++;
297  mAttrCount += attributes.getLength();
[162]298}
299
[170]300void
301X3dParseHandlers::characters(const XMLCh* const chars,
302                             const unsigned int length)
[162]303{
[176]304  mCharacterCount += length;
[162]305}
306
[170]307void
308X3dParseHandlers::ignorableWhitespace(const XMLCh* const chars,
309                                      const unsigned int length)
[162]310{
[176]311  mSpaceCount += length;
[162]312}
313
[170]314void
315X3dParseHandlers::resetDocument()
[162]316{
[176]317  mAttrCount = 0;
318  mCharacterCount = 0;
319  mElementCount = 0;
320  mSpaceCount = 0;
[162]321}
322
[170]323
324
325// ---------------------------------------------------------------------------
326//  StdInParseHandlers: Overrides of the SAX ErrorHandler interface
327// ---------------------------------------------------------------------------
328void
329X3dParseHandlers::error(const SAXParseException& e)
[162]330{
[170]331  XERCES_STD_QUALIFIER cerr << "\nError at (file " << StrX(e.getSystemId())
332                            << ", line " << e.getLineNumber()
333                            << ", char " << e.getColumnNumber()
334                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
335}
[162]336
[170]337void
338X3dParseHandlers::fatalError(const SAXParseException& e)
[162]339{
[170]340  XERCES_STD_QUALIFIER cerr << "\nFatal Error at (file " << StrX(e.getSystemId())
341                            << ", line " << e.getLineNumber()
342                            << ", char " << e.getColumnNumber()
343                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
[162]344}
345
[170]346void
347X3dParseHandlers::warning(const SAXParseException& e)
[162]348{
[170]349  XERCES_STD_QUALIFIER cerr << "\nWarning at (file " << StrX(e.getSystemId())
350                            << ", line " << e.getLineNumber()
351                            << ", char " << e.getColumnNumber()
352                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
[162]353}
354
[170]355
356bool
357X3dParser::ParseFile(const string filename,
[657]358                                         SceneGraphNode **root,
359                                         const bool loadPolygonsAsMeshes)
[170]360{
361  // Initialize the XML4C system
362  try {
363    XMLPlatformUtils::Initialize();
364  }
365 
366  catch (const XMLException& toCatch)
367    {
368      XERCES_STD_QUALIFIER cerr << "Error during initialization! Message:\n"
369                                << StrX(toCatch.getMessage()) << XERCES_STD_QUALIFIER endl;
370      return false;
371    }
372 
373 
374  //
375  //  Create a SAX parser object. Then, according to what we were told on
376  //  the command line, set the options.
377  //
378  SAXParser* parser = new SAXParser;
379  parser->setValidationScheme(valScheme);
380  parser->setDoNamespaces(doNamespaces);
381  parser->setDoSchema(doSchema);
382  parser->setValidationSchemaFullChecking(schemaFullChecking);
383 
384
385  //
386  //  Create our SAX handler object and install it on the parser, as the
387  //  document and error handler. We are responsible for cleaning them
388  //  up, but since its just stack based here, there's nothing special
389  //  to do.
390  //
391  *root = new SceneGraphNode;
[657]392  X3dParseHandlers handler(*root, loadPolygonsAsMeshes);
[170]393  parser->setDocumentHandler(&handler);
394  parser->setErrorHandler(&handler);
395 
396  unsigned long duration;
397  int errorCount = 0;
398  // create a faux scope so that 'src' destructor is called before
399  // XMLPlatformUtils::Terminate
400  {
401    //
402    //  Kick off the parse and catch any exceptions. Create a standard
403    //  input input source and tell the parser to parse from that.
404    //
405    //    StdInInputSource src;
406    try
407      {
408        const unsigned long startMillis = XMLPlatformUtils::getCurrentMillis();
409        parser->parse(filename.c_str());
410        const unsigned long endMillis = XMLPlatformUtils::getCurrentMillis();
411        duration = endMillis - startMillis;
412        errorCount = parser->getErrorCount();
413      }
414    catch (const OutOfMemoryException&)
415      {
416        XERCES_STD_QUALIFIER cerr << "OutOfMemoryException" << XERCES_STD_QUALIFIER endl;
417        errorCount = 2;
418        return false;
419      }
420    catch (const XMLException& e)
421      {
422        XERCES_STD_QUALIFIER cerr << "\nError during parsing: \n"
423                                  << StrX(e.getMessage())
424                                  << "\n" << XERCES_STD_QUALIFIER endl;
425        errorCount = 1;
426        return false;
427      }
[176]428
[170]429   
430    // Print out the stats that we collected and time taken
431    if (!errorCount) {
432      XERCES_STD_QUALIFIER cout << filename << ": " << duration << " ms ("
[176]433                                << handler.GetElementCount() << " elems, "
434                                << handler.GetAttrCount() << " attrs, "
435                                << handler.GetSpaceCount() << " spaces, "
436                                << handler.GetCharacterCount() << " chars)" << XERCES_STD_QUALIFIER endl;
[170]437    }
438  }
439 
440  //
441  //  Delete the parser itself.  Must be done prior to calling Terminate, below.
442  //
443  delete parser;
444 
445  XMLPlatformUtils::Terminate();
446 
447  if (errorCount > 0)
448    return false;
449  else
450    return true;
[162]451}
452
[170]453
[260]454
[508]455/************************************************************************/
456/*             class X3dViewCellsParseHandlers implementation           */
457/************************************************************************/
[260]458
459
460// ---------------------------------------------------------------------------
461//  StdInParseHandlers: Constructors and Destructor
462// ---------------------------------------------------------------------------
[439]463X3dViewCellsParseHandlers::X3dViewCellsParseHandlers(ViewCellsManager *viewCellsManager,
[657]464                                                                                                         const float viewCellHeight):
[490]465mElementCount(0),
466mAttrCount(0),
467mCharacterCount(0),
468mSpaceCount(0),
469mViewCellsManager(viewCellsManager),
470mViewCellHeight(viewCellHeight)
[260]471{
472}
473
[261]474X3dViewCellsParseHandlers::~X3dViewCellsParseHandlers()
[260]475{
476}
477
478
479// ---------------------------------------------------------------------------
480//  StdInParseHandlers: Implementation of the SAX DocumentHandler interface
481// ---------------------------------------------------------------------------
[261]482void X3dViewCellsParseHandlers::endElement(const XMLCh* const name)
[260]483{
484  StrX lname(name);
485  string element(lname.LocalForm());
486  if (element == "Shape")
487    EndShape();
488}
489
490void
[261]491X3dViewCellsParseHandlers::EndShape()
[260]492{
493}
494
495void
[261]496X3dViewCellsParseHandlers::StartIndexedFaceSet(
[260]497                                      AttributeList&  attributes)
498{
499        int len = attributes.getLength();
500        int i;
[262]501        // clear previous vertex indices
502        mCurrentVertexIndices.clear();
[260]503        for (i=0; i < len; i++)
504        {
505                string attrName(StrX(attributes.getName(i)).LocalForm());
506           
507                if (attrName == "coordIndex")
508                {
509                        StrX attrValue(attributes.getValue(i));
510                       
511                        // handle coordIndex
512                        const char *ptr = attrValue.LocalForm();
513                        char *endptr;
514               
[261]515                        while (1)
[260]516                        {
517                                int index = strtol(ptr, &endptr, 10);
518                               
[261]519                                if (ptr == endptr)
520                                        break;
521
522                                if (index != -1)
[260]523                                {
[261]524                                        mCurrentVertexIndices.push_back(index);
[260]525                                }
526                   
527                                ptr = endptr;
528                        }
529                }
530        }
531}
532
533
534void
[439]535X3dViewCellsParseHandlers::StartCoordinate(AttributeList&  attributes)
[260]536{
537        int len = attributes.getLength();
538       
539        VertexContainer vertices;
[364]540        int i;
541        for (i=0; i < len; i++)
[260]542        {
543                string attrName(StrX(attributes.getName(i)).LocalForm());
544               
545                if (attrName == "point")
546                {
547                        StrX attrValue(attributes.getValue(i));
548                       
549                        const char *ptr = attrValue.LocalForm();
550                       
551                        char *endptr;
552                       
553                        while (1)
554                        {
[469]555                                float x = (float)strtod(ptr, &endptr);
[260]556               
557                                if (ptr == endptr)
558                                        break;
559                                ptr = endptr;
560                               
[469]561                                float y = (float)(float)strtod(ptr, &endptr);
[260]562
563                               
564                                if (ptr == endptr)
565                                        break;
566                                ptr = endptr;
567
[469]568                                float z = (float)(float)strtod(ptr, &endptr);
[260]569
570                                if (ptr == endptr)
571                                        break;
572
573                                ptr = endptr;
574                                if (*ptr == ',')
575                                        ptr++;
576
577                                Vector3 v(x, y, z);
[262]578                                vertices.push_back(v);                         
[260]579                        }
580                }
581        }
[261]582
[333]583        for (i = 0; i < mCurrentVertexIndices.size(); i += 3)
[439]584        {
[312]585                Triangle3 baseTri(vertices[mCurrentVertexIndices[i + 0]],
586                                                  vertices[mCurrentVertexIndices[i + 1]],
587                                                  vertices[mCurrentVertexIndices[i + 2]]);
[261]588
[262]589                // create view cell from base triangle
[490]590                mViewCellsManager->AddViewCell(
591                        mViewCellsManager->ExtrudeViewCell(baseTri,
592                        mViewCellHeight));
[439]593        }
[260]594}
595
596
597void
[261]598X3dViewCellsParseHandlers::startElement(const XMLCh* const name,
[439]599                                                                                AttributeList&  attributes)
[260]600{
601  StrX lname(name);
602  string element(lname.LocalForm());
603 
604  if (element == "IndexedFaceSet") {
[261]605    // create the viewcells from individual triangles
[260]606    StartIndexedFaceSet(attributes);
607  }
[261]608       
[260]609  if (element == "Coordinate") {
[261]610          // add coordinates to the triangles
[260]611      StartCoordinate(attributes);
612  }
[508]613  // do nothing
614  //if (element == "Shape") {}
[261]615  // ignore material
616  //if (element == "Material") {}
[260]617
[508]618  ++ mElementCount;
[260]619  mAttrCount += attributes.getLength();
620}
621
622void
[261]623X3dViewCellsParseHandlers::characters(const XMLCh* const chars,
[260]624                             const unsigned int length)
625{
626  mCharacterCount += length;
627}
628
629void
[261]630X3dViewCellsParseHandlers::ignorableWhitespace(const XMLCh* const chars,
[260]631                                      const unsigned int length)
632{
633  mSpaceCount += length;
634}
635
636void
[261]637X3dViewCellsParseHandlers::resetDocument()
[260]638{
639  mAttrCount = 0;
640  mCharacterCount = 0;
641  mElementCount = 0;
642  mSpaceCount = 0;
643}
644
645
646
647// ---------------------------------------------------------------------------
648//  StdInParseHandlers: Overrides of the SAX ErrorHandler interface
649// ---------------------------------------------------------------------------
650void
[261]651X3dViewCellsParseHandlers::error(const SAXParseException& e)
[260]652{
653  XERCES_STD_QUALIFIER cerr << "\nError at (file " << StrX(e.getSystemId())
654                            << ", line " << e.getLineNumber()
655                            << ", char " << e.getColumnNumber()
656                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
657}
658
659void
[261]660X3dViewCellsParseHandlers::fatalError(const SAXParseException& e)
[260]661{
662  XERCES_STD_QUALIFIER cerr << "\nFatal Error at (file " << StrX(e.getSystemId())
663                            << ", line " << e.getLineNumber()
664                            << ", char " << e.getColumnNumber()
665                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
666}
667
668void
[261]669X3dViewCellsParseHandlers::warning(const SAXParseException& e)
[260]670{
671  XERCES_STD_QUALIFIER cerr << "\nWarning at (file " << StrX(e.getSystemId())
672                            << ", line " << e.getLineNumber()
673                            << ", char " << e.getColumnNumber()
674                            << "): " << StrX(e.getMessage()) << XERCES_STD_QUALIFIER endl;
675}
676
677
678bool
[439]679X3dParser::ParseFile(const string filename, ViewCellsManager &viewCells)
[260]680{
681  // Initialize the XML4C system
682  try {
683    XMLPlatformUtils::Initialize();
684  }
685 
686  catch (const XMLException& toCatch)
687    {
688      XERCES_STD_QUALIFIER cerr << "Error during initialization! Message:\n"
689                                << StrX(toCatch.getMessage()) << XERCES_STD_QUALIFIER endl;
690      return false;
691    }
692 
693 
694  //
695  //  Create a SAX parser object. Then, according to what we were told on
696  //  the command line, set the options.
697  //
698  SAXParser* parser = new SAXParser;
699  parser->setValidationScheme(valScheme);
700  parser->setDoNamespaces(doNamespaces);
701  parser->setDoSchema(doSchema);
702  parser->setValidationSchemaFullChecking(schemaFullChecking);
703 
704
705  //
706  //  Create our SAX handler object and install it on the parser, as the
707  //  document and error handler. We are responsible for cleaning them
708  //  up, but since its just stack based here, there's nothing special
709  //  to do.
710  //
[312]711  X3dViewCellsParseHandlers handler(&viewCells, mViewCellHeight);
[260]712  parser->setDocumentHandler(&handler);
713  parser->setErrorHandler(&handler);
714 
715  unsigned long duration;
716  int errorCount = 0;
717  // create a faux scope so that 'src' destructor is called before
718  // XMLPlatformUtils::Terminate
719  {
720    //
721    //  Kick off the parse and catch any exceptions. Create a standard
722    //  input input source and tell the parser to parse from that.
723    //
724    //    StdInInputSource src;
725    try
726      {
727        const unsigned long startMillis = XMLPlatformUtils::getCurrentMillis();
728        parser->parse(filename.c_str());
729        const unsigned long endMillis = XMLPlatformUtils::getCurrentMillis();
730        duration = endMillis - startMillis;
731        errorCount = parser->getErrorCount();
732      }
733    catch (const OutOfMemoryException&)
734      {
735        XERCES_STD_QUALIFIER cerr << "OutOfMemoryException" << XERCES_STD_QUALIFIER endl;
736        errorCount = 2;
737        return false;
738      }
739    catch (const XMLException& e)
740      {
741        XERCES_STD_QUALIFIER cerr << "\nError during parsing: \n"
742                                  << StrX(e.getMessage())
743                                  << "\n" << XERCES_STD_QUALIFIER endl;
744        errorCount = 1;
745        return false;
746      }
747
748   
749    // Print out the stats that we collected and time taken
750    if (!errorCount) {
751      XERCES_STD_QUALIFIER cout << filename << ": " << duration << " ms ("
752                                << handler.GetElementCount() << " elems, "
753                                << handler.GetAttrCount() << " attrs, "
754                                << handler.GetSpaceCount() << " spaces, "
755                                << handler.GetCharacterCount() << " chars)" << XERCES_STD_QUALIFIER endl;
756    }
757  }
758 
759  //
760  //  Delete the parser itself.  Must be done prior to calling Terminate, below.
761  //
762  delete parser;
763 
764  XMLPlatformUtils::Terminate();
765 
766  if (errorCount > 0)
767    return false;
768  else
769    return true;
[333]770}
Note: See TracBrowser for help on using the repository browser.