views:

50

answers:

3

So this is what I am trying to accomplish. I am trying to use a sax parser to parse some XML. it looks like I need to call all their methods as statics. So if I want to pass a value back from say startElement it is static void startElement. Which brings me to my example code. I have been pulling my hair on how to update a value in a Nesting class from a static member function.

I have looked at several things such as defining OuterClass * oc; then trying to reference oc->allRecords, but since it is a static method inside, it fails. I am sure I am doing something wrong architecturally, so any feedback on what would be the right way to do this would be a great help. Thanks.

class Attribute {
    string AttributeName;
    string AttributeValue;
};
typedef shared_ptr<Attribute> AttributePtr;

class AttributeSet {
    vector<AttributePtr> Attributes;
};
typedef shared_ptr<AttributeSet> AttributeSetPtr;

class OuterClass {
    public :
    vector<AttributeSetPtr> allRecords;
    class InnerClass {
         public:
         static mymethod1() {
            // I need to be able to set attributes here :
            // This would be the characters method for sax parsing 
            // What is the right way to Attributes.push_back(new Attribute(Name,Value));
         }


         static mymethod2() {
            // I also need to be able to add Records here :
            // This would be the endElement for sax parsing
            //  What is the right way to allRecords.push_back(AttributeSet);
         }
   };

   // EDIT: CALLING CODE GOES HERE (WAS EDITED - SEE BELOW) 
};



// ADDING INFORMATION REGARDING HOW METHOD 1 & 2 are called
xmlSAXHandler saxHandler;
memset(&saxHandler, 0, sizeof(saxHandler));
saxHandler.initialized = XML_SAX2_MAGIC;
...
saxHandler.endElementsNs = &InnerClass::method2;
saxHandler.characters = &InnerClass::method1;
...
InnerClass innerXmlParsingClass
xmlSaxUserParseMemory( &saxHandler, &innerXmlParsingClass, xmlString, xmlString.length());
+3  A: 

Your mistake is using an inner class (are you coming from Java?).

I don't know what you believe you are are achieving with an inner class, but it won't work. Don't use inner classes in C++ unless you really know what it does (for inner classes, protected and private members of the outer classes are seen as if they were public).

Now, as the solution to your problem, I guess it depends on the implementation you're using (I used once Apache's Xerces SAX, but I know Microsoft offers its own SAX implementation, and that there should be a lot other alternatives, so...)

Edit

After the comment, I found the following tutorial:

http://www.jamesh.id.au/articles/libxml-sax/libxml-sax.html

I must say that, coming from Java to C++, and using a C API, you have a kind of courage...

:-D

If you are not familiar enough with function pointers, and C in general, using libxml2 will be a challenge. Be sure that in the end, you will understand those notions... Note that C have a way to handle the data that C++, Java or C# developers associate to this. The C way is to pass a pointer to your data (the user data) to a function, and when the callback is called, it passes back this pointer, typed as a void *. You must then cast it back to its right type, and voilà, you have your this back.

:-)

Anyway, reading the doc, I see that when you parse the file, you'll call the following C function:

int xmlSAXUserParseFile(   xmlSAXHandlerPtr   sax,
                           void *             user_data,
                           const char *       filename);

the user_data part is the one that interest you because it enables you to have a context. So, wrapping this function in a C++ class, you could have something like:

// MySaxBase.hpp
class MySaxBase
{
   public :
       MySaxBase() ;
       int parseFile(const std::string & p_filename) ;
       virtual void startDocument() ;
       virtual void endDocument() ;
   private :
       static void do_startDocument(void *p_user_data) ;
       static void do_endDocument(void *p_user_data) ;
       xmlSAXHandler     m_sax ;
}

.

// MySaxBase.cpp

extern "C"
{

void do_startDocument(void *p_user_data)
{
   // this static method will convert the p_user_data into
   // the this pointer...
   MySaxBase * saxBase = static_cast<MySaxBase *>(p_user_data) ;
   // ...and call the right virtual method
   saxBase->startDocument() ;
}

void do_endDocument(void *p_user_data)
{
   // this static method will convert the p_user_data into
   // the this pointer...
   MySaxBase * saxBase = static_cast<MySaxBase *>(p_user_data) ;
   // ...and call the right virtual method
   saxBase->endDocument() ;
}

} // extern "C"

MySaxBase::MySaxBase()
{
   // the m_sax structure must be set to zero to NULL all its
   // pointers to functions
   memset(&m_sax, 0, sizeof(xmlSAXHandler)) ;
   // Now, we initialize some pointers to the static method we
   // want to be called
   this->m_sax.startDocument = do_startDocument ;
   this->m_sax.endDocument = do_endDocument ;
}

int MySaxBase::parseFile(const std::string & p_filename)
{
   // the important thing, here, is the this pointer, passed as
   // a user_data parameter
   return xmlSAXUserParseFile(&m_sax, this, p_filename.c_str()) ;
}

void MySaxBase::startDocument()
{
   // The document started. Override this method to
   // actually do something
}

void MySaxBase::endDocument()
{
   // The document ended. Override this method to
   // actually do something
}

I did not test this, and I never used libxml2, but I guess the code must be Ok, and this should be enough for you to continue on your own: Just add the methods you want to support, initialize the sax handler with the relevant function pointers, and you'll have your class complete.

The MySaxBase::startDocument and MySaxBase::endDocument methods are virtual just for you to derive from MySaxBase and then override those methods.

Edit 2

I'll reproduce here Steve Jessop's excellent comment:

+1. One tiny quibble - I don't think that static member functions are guaranteed by the C++ standard to have C linkage / calling convention, but to use them as a callback from a C API, that's what they need. I don't specifically know what implementations it makes a difference, but for safety do_startDocument should be a free function declared with extern "C". On the same subject: a Java programmer may not realise you have make sure that the function can't throw an exception (because C doesn't have them). So you'd normally want to see a try/catch(...) in the wrapper function. – Steve Jessop

Following this, and after reading Johannes Schaub - litb (who else?) no less excellent answer at http://stackoverflow.com/questions/592160/static-vs-extern-c/592205#592205 , I modified the code to make do_startDocument and do_endDocument real C functions (i.e. wrapped in an extern "C" block). This usually is not important (I never encountered this kind of problem), but, better safe than sorry.

paercebal
+1 for the observation that OP might be using the wrong construct to begin with.
John Dibling
Yes, coming from Java, hence my issues.. Trying to implement sax w/ libxml2
Chrispix
+1. One tiny quibble - I don't think that `static` member functions are guaranteed by the C++ standard to have C linkage / calling convention, but to use them as a callback from a C API, that's what they need. I don't specifically know what implementations it makes a difference, but for safety `do_startDocument` should be a free function declared with `extern "C"`. On the same subject: a Java programmer may not realise you have make sure that the function can't throw an exception (because C doesn't have them). So you'd normally want to see a `try/catch(...)` in the wrapper function.
Steve Jessop
@Steve Jessop: +1 for the comment, which led me to this litb's answer at http://stackoverflow.com/questions/592160/static-vs-extern-c/592205#592205 , and then to the modification of my code. Thanks !
paercebal
A: 

Your basic problem is that static methods are not per-instance, so there is no this pointer. You somehow need to get a OuterClass* passed to mymethod1 and mymethod2.

If you show us how mymethod1 and mymethod2 are called, we can help you further.

If it's simply called by you someplace where you have a OuterClass object, then your solution is simple:

class OuterClass
{
// ...
  static void mymethod1(OuterClass* oc)
  {
    oc->all_records.push_back( something );
  }
};

void some_func()
{
  OuterClass oc;
  OuterClass::method1(&oc);
}
John Dibling
I updated my question with some additional code..
Chrispix
+1  A: 

Since you updated your question here is how you should do this:

class OuterClass {
public:
    vector<AttributeSetPtr> allRecords;

    void characters(const xmlChar* ch, int len)
    {
        // do here whatever you want
        allRecords.push_back(bla bla);
    }

    static void static_characters(void* ctx, const xmlChar* ch, int len) {
        // retrieve this pointer from ctx
        static_cast<OuterClass*>(ctx)->characters(ch, len);
    }
};

saxHandler.characters = &OuterClass::static_characters;
...
OuterClass outerClass;
xmlSaxUserParseMemory(&saxHandler, static_cast<void*>(&outerClass), xmlString, xmlString.length());
ybungalobill