I only have a little experience in this style of coding. I offer my suggestion with the expectation that it will more likely than not be trumped by a more experienced responder. It's also a bit long; you have been warned.
I had a similar problem with
Enigma Cipher. I wanted to be able to easily add new forms of encryption that could be included with newly written levels. I encapsulated a "layer" of encryption in a class called Layer. This allowed the game's engine to be able to run the encryption for a given level without knowledge of how that encryption worked. (In my case, it also allowed layers to reference other layers to create "hybrid" results; this may or may not be useful for your applications, depending on what "abilities" you intend to add to your cards.) The end result was that there was only one section of my code that had to worry about what different types of layers existed, namely, the main function that actually initialized level objects. Aside from this, the layers' functions were called when they might be needed to do something, and the layers took care of their own gritty details.
If I were in your position (I'm assuming C++, as that's the language I'm most proficient in), I would create an abstract class to encapsulate any card ability with generically defined virtual functions for any context under which a card ability might become relevant, with a passed in parameter that references a structure/class storing game state. For example, this class would probably have a start of turn function, an end of turn function, a prepare to draw from deck function, a draw from deck function, and a draw this card from deck function, among other things, and the vast majority of these functions would do nothing (except perhaps ones like "discard this card" or "draw this card", which would then perform their corresponding default functions). I would also imagine a const (or const-like) int to be declared uniquely in each child; that would allow you to give an order of execution for abilities on cards. Cards themselves would be a class that contain, among other things, a vector/deque of pointers to instances of the ability class described earlier.
Abilities would inherit from this abstract class, and would only override the functions that they were relevant to. For instance, if dredge from MtG was to be implemented in such a way, it would only override the "prepare to draw a card" function and, in a crude implementation, would offer the user the option to dredge that card if it was in the graveyard and, if they accepted, would discard X cards from the deck (X being a parameter to initialize the class stored in a child class variable) through calling the discard function of the top X cards of the deck in order (possibly triggering other card effects), add the dredged card to the hand, and set the game state to post-draw before returning. Aside from this, the child class wouldn't have any other implementation (dredge doesn't do anything else).
The game engine would do little itself; it would store a game state struct/object, and repeatedly call the ability functions of each card present in a game loop (changing the game state to the next in most cases). The actual function(s) of cards, if any, would be implemented in the calls to virtual functions in most cases. Some game mechanics, such as a battle phase, that affect the entire field may be best done outside of the scope of any individual card (either built into the game engine or its own engine); you may even want to add an ability pointer vector to whatever entity you have serve this purpose, and add things like battle phase functionality as unique "abilities" that the battlefield itself has.
The code that generates your cards (whether through XML like you suggest or through hardcoding like I use in Enigma Cipher) would somehow need to be aware of what different ability child classes exist in order to initiate children of those classes. If you implement an XML parser, you *
might* be able to get around this by making a function pointer vector/deque in the header file of your ability parent class, have sections of
global scope code in your ability children class's header files that, in each section of code, adds to this vector/deque a pointer to a static member function of the corresponding class, and making your XML generating code call each function of this vector/deque to determine whether the XML should generate a new ability and, if so, return a pointer to this dynamically allocated ability. I'm not even sure if code like that would compile/work, but if it did, your XML parsing code would be able to automatically call helper functions for each child class simply by including each child class in its header file, and this list of includes would literally be the *only* section of code that would need to know about the various types of card abilities that exist.
I hope this made sense, and was helpful. I might not have been clear in my explanation.