/** Implementation for NSXMLParser for GNUStep
   Copyright (C) 2004 Free Software Foundation, Inc.

   Written by:  Richard Frith-Macdonald <rfm@gnu.org>
   Date: May 2004

   This file is part of the GNUstep Base Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02111 USA.

   */

#include "config.h"
#include <Foundation/NSArray.h>
#include <Foundation/NSError.h>
#include <Foundation/NSEnumerator.h>
#include <Foundation/NSException.h>
#include <Foundation/NSXMLParser.h>
#include <Foundation/NSData.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSNull.h>

NSString* const NSXMLParserErrorDomain = @"NSXMLParserErrorDomain";

static  NSNull  *null = nil;

#if	 defined(HAVE_LIBXML)

/* We support a strict libxml2 based parser ... but sometimes we need a
 * sloppier parser which copes with bad XML generated by Apple's software,
 * so we call the sloppy parser GSSloppyXMLParser.
 */
#define SloppyXMLParser  GSSloppyXMLParser
@interface      GSSloppyXMLParser : NSXMLParser
@end

#include <Additions/GNUstepBase/GSXML.h>

@interface	NSXMLSAXHandler : GSSAXHandler
{
@public
  id		_delegate;      // Not retained
  id		_owner;         // Not retained
  NSError	*_lastError;
  BOOL		_shouldProcessNamespaces;
  BOOL		_shouldReportNamespacePrefixes;
  BOOL		_shouldResolveExternalEntities;
  NSMutableArray        *_namespaces;
}
- (void) _setOwner: (id)owner;
@end

@implementation	NSXMLSAXHandler

- (void) dealloc
{
  DESTROY(_namespaces);
  DESTROY(_lastError);
  [super dealloc];
}

- (id) init
{
  if ((self = [super init]) != nil)
    {
      _namespaces = [NSMutableArray new];
    }
  return self;
}

- (void) endDocument
{
  [_delegate parserDidEndDocument: _owner];
}
- (void) startDocument
{
  [_delegate parserDidStartDocument: _owner];
}

- (void) startElement: (NSString*)elementName
	       prefix: (NSString*)prefix
		 href: (NSString*)href
	   attributes: (NSMutableDictionary*)elementAttributes
	   namespaces: (NSMutableDictionary*)elementNamespaces
{
  NSString      *qName = elementName;

  if ([prefix length] > 0)
    {
      qName = [NSString stringWithFormat: @"%@:%@", prefix, qName];
    }

  if ([elementNamespaces count] > 0)
    {
      [_namespaces addObject: [elementNamespaces allKeys]];
      if (_shouldReportNamespacePrefixes)
        {
          NSEnumerator  *e = [elementNamespaces keyEnumerator];
          NSString      *k;

          while ((k = [e nextObject]) != nil)
            {
              NSString  *v = [elementNamespaces objectForKey: k];

              [_delegate parser: _owner
                didStartMappingPrefix: k
                toURI: v];
            }
        }
    }
  else
    {
      [_namespaces addObject: null];
    }

  if (_shouldProcessNamespaces)
    {
      [_delegate parser: _owner
	didStartElement: elementName
	   namespaceURI: href
	  qualifiedName: qName
	     attributes: elementAttributes];
    }
  else
    {
      /* When we are not handling namespaces specially, any namespaces
       * should appear as attributes of the element.
       */
      if ([elementNamespaces count] > 0)
        {
          NSEnumerator  *e = [elementNamespaces keyEnumerator];
          NSString      *k;

          if (elementAttributes == nil)
            {
              elementAttributes = [NSMutableDictionary dictionary];
            }
          while ((k = [e nextObject]) != nil)
            {
              NSString  *v = [elementNamespaces objectForKey: k];

              if ([k length] == 0)
                {
                  [elementAttributes setObject: v forKey: @"xmlns"];
                }
              else
                {
                  k = [@"xmlns:" stringByAppendingString: k];
                  [elementAttributes setObject: v forKey: k];
                }
            }
        }
      [_delegate parser: _owner
	didStartElement: qName
	   namespaceURI: nil
	  qualifiedName: nil
	     attributes: elementAttributes];
    }
}

- (void) endElement: (NSString*)elementName
	     prefix: (NSString*)prefix
	       href: (NSString*)href
{
  NSString      *qName = elementName;

  if ([prefix length] > 0)
    {
      qName = [NSString stringWithFormat: @"%@:%@", prefix, qName];
    }
  if (_shouldProcessNamespaces)
    {
      [_delegate parser: _owner
	  didEndElement: elementName
	   namespaceURI: href
	  qualifiedName: qName];
    }
  else
    {
      [_delegate parser: _owner
	  didEndElement: qName
	   namespaceURI: nil
	  qualifiedName: nil];
    }

  if (_shouldReportNamespacePrefixes)
    {
      id        o = [_namespaces lastObject];

      if (o != (id)null)
        {
          NSEnumerator  *e = [(NSArray*)o objectEnumerator];
          NSString      *k;

          while ((k = [e nextObject]) != nil)
            {
              [_delegate parser: _owner didEndMappingPrefix: k];
            }
        }
    }
  [_namespaces removeLastObject];
}
- (void) attribute: (NSString*) name value: (NSString*)value
{
	// FIXME
}
- (void) characters: (NSString*)string
{
  [_delegate parser: _owner
    foundCharacters: string];
}
- (void) ignoreWhitespace: (NSString*) ch
{
  [_delegate parser: _owner
    foundIgnorableWhitespace: ch];
}
- (void) processInstruction: (NSString*)targetName data: (NSString*)PIdata
{
  [_delegate parser: _owner
    foundProcessingInstructionWithTarget: targetName
    data: PIdata];
}
- (void) comment: (NSString*) value
{
  [_delegate parser: _owner
    foundComment: value];
}
- (void) cdataBlock: (NSData*)value
{
  [_delegate parser: _owner
    foundCDATA: value];
}

/**
 * Called to return the filename from which an entity should be loaded.
 */
- (NSString*) loadEntity: (NSString*)publicId
		      at: (NSString*)location
{
  return nil;
}

/**
 * An old global namespace has been parsed.
 */
- (void) namespaceDecl: (NSString*)name
		  href: (NSString*)href
		prefix: (NSString*)prefix
{
}

- (void) notationDecl: (NSString*)name
	       public: (NSString*)publicId
	       system: (NSString*)systemId
{
  [_delegate parser: _owner
    foundNotationDeclarationWithName: name
    publicID: publicId
    systemID: systemId];
}

/**
 * An entity definition has been parsed.
 */
- (void) entityDecl: (NSString*)name
	       type: (int)type
	     public: (NSString*)publicId
	     system: (NSString*)systemId
	    content: (NSString*)content
{
}

- (void) attributeDecl: (NSString*)nameElement
		  name: (NSString*)name
		  type: (int)type
	  typeDefValue: (int)defType
	  defaultValue: (NSString*)value
{
  [_delegate parser: _owner
    foundAttributeDeclarationWithName: name
    forElement: nameElement
    type: nil		// FIXME
    defaultValue: value];
}

- (void) elementDecl: (NSString*)name
		type: (int)type
{
  [_delegate parser: _owner
    foundElementDeclarationWithName: name
    model: nil];	// FIXME
}

/**
 * What to do when an unparsed entity declaration is parsed.
 */
- (void) unparsedEntityDecl: (NSString*)name
		     public: (NSString*)publicId
		     system: (NSString*)systemId
	       notationName: (NSString*)notation
{
}

/**
 * Called when an entity reference is detected.
 */
- (void) reference: (NSString*) name
{
}

/**
 * An old global namespace has been parsed.
 */
- (void) globalNamespace: (NSString*)name
		    href: (NSString*)href
		  prefix: (NSString*)prefix
{
}

/**
 * Called when a warning message needs to be output.
 */
- (void) warning: (NSString*)e
{
  GSPrintf(stderr, @"%@", e);
}

- (void) error: (NSString*)e
{
  NSError	*error;
  NSDictionary	*d;

  d = [NSDictionary dictionaryWithObjectsAndKeys:
    e, NSLocalizedDescriptionKey,
    nil];
  error = [NSError errorWithDomain: NSXMLParserErrorDomain
			      code: 0
			  userInfo: d];
  ASSIGN(_lastError, error);
  [_delegate parser: _owner
    parseErrorOccurred: error];
}
- (void) fatalError: (NSString*)e
{
  [self error: e];
}
- (void) warning: (NSString*)e
       colNumber: (int)colNumber
      lineNumber: (int)lineNumber
{
  e = [NSString stringWithFormat: @"at line: %d column: %d ... %@",
    lineNumber, colNumber, e];
  [self warning: e];
}
- (void) error: (NSString*)e
     colNumber: (int)colNumber
    lineNumber: (int)lineNumber
{
  e = [NSString stringWithFormat: @"at line: %d column: %d ... %@",
    lineNumber, colNumber, e];
  [self error: e];
}
- (void) fatalError: (NSString*)e
       colNumber: (int)colNumber
      lineNumber: (int)lineNumber
{
  e = [NSString stringWithFormat: @"at line: %d column: %d ... %@",
    lineNumber, colNumber, e];
  [self fatalError: e];
}
- (int) hasInternalSubset
{
  return 0;
}
- (BOOL) internalSubset: (NSString*)name
	     externalID: (NSString*)externalID
	       systemID: (NSString*)systemID
{
  return NO;
}
- (int) hasExternalSubset
{
  return 0;
}
- (BOOL) externalSubset: (NSString*)name
	     externalID: (NSString*)externalID
	       systemID: (NSString*)systemID
{
  return NO;
}
- (void*) getEntity: (NSString*)name
{
  return 0;
}
- (void*) getParameterEntity: (NSString*)name
{
  return 0;
}

- (void) _setOwner: (id)owner
{
  _owner = owner;
}

@end



@implementation NSXMLParser

+ (void) initialize
{
  if (null == nil)
    {
      null = RETAIN([NSNull null]);
    }
}

#define	myParser	((GSXMLParser*)_parser)
#define	myHandler	((NSXMLSAXHandler*)_handler)

- (void) abortParsing
{
  NSDictionary	*d;
  NSString	*e;
  NSError	*error;

  e = @"Parsing aborted";
  d = [NSDictionary dictionaryWithObjectsAndKeys:
    e, NSLocalizedDescriptionKey,
    nil];
  error = [NSError errorWithDomain: NSXMLParserErrorDomain
			      code: 0
			  userInfo: d];
  ASSIGN(myHandler->_lastError, error);
  [myHandler->_delegate parser: myHandler->_owner parseErrorOccurred: error];
  [myParser abortParsing];
}

- (void) dealloc
{
  DESTROY(_parser);
  DESTROY(_handler);
  [super dealloc];
}

- (id) delegate
{
  return myHandler->_delegate;
}

- (id) initWithContentsOfURL: (NSURL*)anURL
{
  NSData	*d = [NSData dataWithContentsOfURL: anURL];

  if (d == nil)
    {
      DESTROY(self);
    }
  else
    {
      self = [self initWithData: d];
    }
  return self;
}

- (id) initWithData: (NSData*)data
{
  _handler = [NSXMLSAXHandler new];
  [myHandler _setOwner: self];
  _parser = [[GSXMLParser alloc] initWithSAXHandler: myHandler withData: data];
  return self;
}

- (BOOL) parse
{
  BOOL	result;

  result = [[myHandler parser] parse];
  return result;
}

- (NSError*) parserError
{
  return nil;	// FIXME
}

- (void) setDelegate: (id)delegate
{
  myHandler->_delegate = delegate;
}

- (void) setShouldProcessNamespaces: (BOOL)aFlag
{
  myHandler->_shouldProcessNamespaces = aFlag;
}

- (void) setShouldReportNamespacePrefixes: (BOOL)aFlag
{
  myHandler->_shouldReportNamespacePrefixes = aFlag;
}

- (void) setShouldResolveExternalEntities: (BOOL)aFlag
{
  myHandler->_shouldResolveExternalEntities = aFlag;
}

- (BOOL) shouldProcessNamespaces
{
  return myHandler->_shouldProcessNamespaces;
}

- (BOOL) shouldReportNamespacePrefixes
{
  return myHandler->_shouldReportNamespacePrefixes;
}

- (BOOL) shouldResolveExternalEntities
{
  return myHandler->_shouldResolveExternalEntities;
}

@end

@implementation NSXMLParser (NSXMLParserLocatorAdditions)
- (int) columnNumber
{
  return [myParser columnNumber];
}

- (int) lineNumber
{
  return [myParser lineNumber];
}

- (NSString*) publicID
{
  return [myParser publicID];
}

- (NSString*) systemID
{
  return [myParser systemID];
}

@end

#else

/* We have no strict libxml based parser, so we use the sloppy parser
 * as the normal NSXMLParser and create a GSSloppyXMLParser subclass
 * so that we can still specify GSSloppyXMLParser in other files.
 */
#define SloppyXMLParser NSXMLParser
@interface      GSSloppyXMLParser : NSXMLParser
@end
@implementation GSSloppyXMLParser
@end
#endif


@implementation NSString (NSXMLParser)

- (NSString *) _stringByExpandingXMLEntities
{
  NSMutableString *t=[NSMutableString stringWithString: self];
  [t replaceOccurrencesOfString: @"&" withString: @"&amp;" options: 0 range: NSMakeRange(0, [t length])];  // must be first!
  [t replaceOccurrencesOfString: @"<" withString: @"&lt;" options: 0 range: NSMakeRange(0, [t length])];
  [t replaceOccurrencesOfString: @">" withString: @"&gt;" options: 0 range: NSMakeRange(0, [t length])];
  [t replaceOccurrencesOfString: @"\"" withString: @"&quot;" options: 0 range: NSMakeRange(0, [t length])];
  [t replaceOccurrencesOfString: @"'" withString: @"&apos;" options: 0 range: NSMakeRange(0, [t length])];
  return t;
}

@end

static NSString *UTF8STR(const void *ptr, int len)
{
  NSString	*s;

  s = [[NSString alloc] initWithBytes: ptr
			       length: len
			     encoding: NSUTF8StringEncoding];
  if (s == nil)
    NSLog(@"could not convert to UTF8 string! bytes=%08x len=%d", ptr, len);
  return AUTORELEASE(s);
}

typedef struct NSXMLParserIvarsType
{
  NSMutableArray        *tagPath;	// hierarchy of tags
  NSMutableArray        *namespaces;
  NSData                *data;
  NSError               *error;
  const unsigned char   *cp;		// character pointer
  const unsigned char   *cend;		// end of data
  int line;				// current line (counts from 0)
  int column;				// current column (counts from 0)
  BOOL abort;				// abort parse loop
  BOOL shouldProcessNamespaces;
  BOOL shouldReportNamespacePrefixes;
  BOOL shouldResolveExternalEntities;
  BOOL acceptHTML;			// be lazy with bad tag nesting
} NSXMLParserIvars;

@implementation SloppyXMLParser

#define EXTRA_DEBUG     0

typedef struct { @defs(NSXMLParser) } *xp;
#define _parser (((xp)self)->_parser)
#define _handler (((xp)self)->_handler)
#define	this		((NSXMLParserIvars*)_parser)
#define	_del	((id)_handler)

+ (void) initialize
{
  if (null == nil)
    {
      null = RETAIN([NSNull null]);
    }
}

- (void) abortParsing
{
  this->abort = YES;
}

- (int) columnNumber
{
  return this->column;
}

- (void) dealloc
{
  if (this != 0)
    {
      RELEASE(this->data);
      RELEASE(this->error);
      RELEASE(this->tagPath);
      RELEASE(this->namespaces);
      NSZoneFree([self zone], this);
      _parser = 0;
      _handler = 0;
    }
  [super dealloc];
}

- (id) delegate
{
  return _del;
}

- (id) initWithContentsOfURL: (NSURL *)anURL
{
  return [self initWithData: [NSData dataWithContentsOfURL: anURL]];
}

- (id) initWithData: (NSData *)data
{
  if (data == nil)
    {
      DESTROY(self);
    }
  else
    {
      self = [super init];
      if (self)
	{
	  _parser = NSZoneMalloc([self zone], sizeof(NSXMLParserIvars));
	  memset(_parser, '\0', sizeof(NSXMLParserIvars));
	  this->data = [data copy];
	  this->tagPath = [[NSMutableArray alloc] init];
	  this->namespaces = [[NSMutableArray alloc] init];
	  this->cp = [this->data bytes];
	  this->cend = this->cp + [this->data length];
	}
    }
  return self;
}

- (int) lineNumber
{
  return this->line;
}

- (void) setDelegate: (id)delegate
{
  _handler = delegate;
}

- (NSError *) parserError
{
  return this->error;
}

- (NSArray *) _tagPath
{
  return this->tagPath;
}

#define cget() ((this->cp < this->cend)?(this->column++, *this->cp++): -1)

- (BOOL) _parseError: (NSString *)message
{
#if EXTRA_DEBUG
  NSLog(@"XML parseError: %@", message);
#endif
  NSError *err = nil;

  ASSIGN(this->error, err);
  this->abort = YES;  // break look
  if ([_del respondsToSelector: @selector(parser:parseErrorOccurred:)])
    [_del parser: self parseErrorOccurred: this->error];  // pass error
  return NO;
}

/* Go up the namespace stack looking for a mapping from p to
 * a URI.  Return the first URI found (or nil if none is found).
 */
- (NSString*) _uriForPrefix: (NSString*)p
{
  unsigned      i = [this->namespaces count];
  NSString      *uri = nil;

  while (uri == nil && i-- > 0)
    {
      id        o = [this->namespaces objectAtIndex: i];

      if (o != (id)null)
        {
          uri = [(NSDictionary*)o objectForKey: p];
        }
    }
  return uri;
}

- (void) _closeLastTag
{
  NSString      *tag = [this->tagPath lastObject];

  if ([_del respondsToSelector:
    @selector(parser:didEndElement:namespaceURI:qualifiedName:)])
    {
      NSString  *qualified = nil;
      NSString  *uri = nil;

      if (this->shouldProcessNamespaces)
        {
          NSRange   r = [tag rangeOfString: @":"];
          NSString  *p = @"";

          qualified = tag;
          if (r.length > 0)
            {
              p = [tag substringToIndex: r.location];
              tag = [tag substringFromIndex: NSMaxRange(r)];
            }
          uri = [self _uriForPrefix: p];
        }
      [_del parser: self
        didEndElement: tag
        namespaceURI: uri
        qualifiedName: qualified];
    }

  if (this->shouldReportNamespacePrefixes)
    {
      if ([_del respondsToSelector:
        @selector(parser:didEndMappingPrefix:)])
        {
          id    d = [this->namespaces lastObject];

          if (d != (id)null)
            {
              NSEnumerator  *e = [(NSDictionary*)d keyEnumerator];
              NSString      *k;

              while ((k = [e nextObject]) != nil)
                {
                  [_del parser: self didEndMappingPrefix: k];
                }
            }
        }
    }

  [this->tagPath removeLastObject];
  [this->namespaces removeLastObject];
}

- (void) _processTag: (NSString *)tag
	       isEnd: (BOOL)flag
      withAttributes: (NSDictionary *)attributes
{
  if (this->acceptHTML)
    {
      tag = [tag lowercaseString];  // not case sensitive
    }
  if (!flag)
    {
      if ([tag isEqualToString: @"?xml"])
	{
#if EXTRA_DEBUG
NSLog(@"parserDidStartDocument: ");
#endif
	  if ([_del respondsToSelector: @selector(parserDidStartDocument:)])
            {
              [_del parserDidStartDocument: self];
            }
	  return;
	}
      else if ([tag hasPrefix: @"?"])
	{
#if EXTRA_DEBUG
NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes);
#endif
	  // parser: foundProcessingInstructionWithTarget: data: 
	  return;
	}
      else if ([tag isEqualToString: @"!DOCTYPE"])
	{
#if EXTRA_DEBUG
NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes);
#endif
	  return;
	}
      else if ([tag isEqualToString: @"!ENTITY"])
	{
#if EXTRA_DEBUG
NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes);
#endif
	  return;
	}
      else if ([tag isEqualToString: @"!CDATA"])
	{
          // pass through as NSData
          // parser: foundCDATA:   
#if EXTRA_DEBUG
NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes);
#endif
          return;
	}
      else
        {
          NSMutableDictionary   *ns = nil;
          NSMutableDictionary   *attr = nil;
          NSEnumerator          *enumerator = [attributes keyEnumerator];
          NSString              *k;
          NSString              *uri;
          NSString              *qualified;

          while ((k = [enumerator nextObject]) != nil)
            {
              NSString  *prefix = nil;

              if ([k isEqualToString: @"xmlns"] == YES)
                {
                  prefix = @"";
                }
              else if ([k hasPrefix: @"xmlns:"] == YES)
                {
                  prefix = [k substringFromIndex: 6];
                }
              if (prefix != nil)
                {
                  if (ns == nil)
                    {
                      ns = [NSMutableDictionary dictionary];
                      if (this->shouldProcessNamespaces)
                        {
                          attr = AUTORELEASE([attributes mutableCopy]);
                        }
                    }
                  uri = [attributes objectForKey: k];
                  [ns setObject: uri forKey: prefix];
                  if (attr != nil)
                    {
                      [attr removeObjectForKey: k];
                    }
                  if (this->shouldReportNamespacePrefixes)
                    {
                      if ([_del respondsToSelector:
                        @selector(parser:didStartMappingPrefix:toURI:)])
                        {
                          [_del parser: self
                            didStartMappingPrefix: prefix
                            toURI: uri];
                        }
                    }
                  if (attr != nil)
                    {
                      attributes = attr;
                    }
                }
            }

          [this->tagPath addObject: tag];
          [this->namespaces addObject: ((ns == nil) ? (id)null : (id)ns)];

          if ([_del respondsToSelector:
  @selector(parser:didStartElement:namespaceURI:qualifiedName:attributes:)])
            {
              if (this->shouldProcessNamespaces)
                {
                  NSRange   r = [tag rangeOfString: @":"];
                  NSString  *p = @"";

                  qualified = tag;
                  if (r.length > 0)
                    {
                      p = [tag substringToIndex: r.location];
                      tag = [tag substringFromIndex: NSMaxRange(r)];
                    }
                  uri = [self _uriForPrefix: p];
                }
              else
                {
                  qualified = nil;
                  uri = nil;
                }
              [_del parser: self
                didStartElement: tag
                namespaceURI: uri
                qualifiedName: qualified
                attributes: attributes];
            }
        }
    }
  else
    {
      // closing tag
      if (this->acceptHTML)
	{
	  // lazily close any missing tags on stack
	  while ([this->tagPath count] > 0
	    && ![[this->tagPath lastObject] isEqualToString: tag])
	    {
              [self _closeLastTag];
	    }
	  if ([this->tagPath count] == 0)
            {
              return;  // ignore closing tag without matching open...
            }
	}
      else if (![[this->tagPath lastObject] isEqualToString: tag])
	{
	  [self _parseError: [NSString stringWithFormat:
	    @"tag nesting error (</%@> expected, </%@> found)",
	    [this->tagPath lastObject], tag]];
	  return;
	}

      [self _closeLastTag];
    }
}

- (NSString *) _entity
{
// parse &xxx; sequence
  int c;
  const unsigned char *ep = this->cp;  // should be position behind &
  int len;
  unsigned int val;
  NSString *entity;

  do {
    c = cget();
  } while (c != EOF && c != '<' && c != ';');

  if (c != ';')
    return nil; // invalid sequence - end of file or missing ; before next tag
  len = this->cp - ep - 1;
  if (*ep == '#')
    {
// &#ddd; or &#xhh;
      // !!! ep+1 is not 0-terminated - but by ;!!
    if (sscanf((char *)ep+1, "x%x;", &val))
      return [NSString stringWithFormat: @"%C", val];  // &#xhh; hex value
    else if (sscanf((char *)ep+1, "%d;", &val))
      return [NSString stringWithFormat: @"%C", val];  // &ddd; decimal value
    }
  else
    {
// the five predefined entities
    if (len == 3 && strncmp((char *)ep, "amp", len) == 0)
      return @"&";
    if (len == 2 && strncmp((char *)ep, "lt", len) == 0)
      return @"<";
    if (len == 2 && strncmp((char *)ep, "gt", len) == 0)
      return @">";
    if (len == 4 && strncmp((char *)ep, "quot", len) == 0)
      return @"\"";
    if (len == 4 && strncmp((char *)ep, "apos", len) == 0)
      return @"'";
    }
  entity = UTF8STR(ep, len);
#if 1
  NSLog(@"NSXMLParser: unrecognized entity: &%@;", entity);
#endif
//  entity=[entitiesTable objectForKey: entity];  // look up string in entity translation table
  if (!entity)
    entity=@"&??;";  // unknown entity
  return entity;
}

- (NSString *) _qarg
{
// get argument (might be quoted)
  const unsigned char *ap = --this->cp;  // argument start pointer
  int c = cget();  // refetch first character

#if EXTRA_DEBUG
  NSLog(@"_qarg: %02x %c", c, isprint(c)?c: ' ');
#endif
  if (c == '\"')
    {
// quoted argument
      do {
        c = cget();
        if (c == EOF)
          return nil;  // unterminated!
      } while (c != '\"');
    return UTF8STR(ap + 1, this->cp - ap - 2);
    }
  if (c == '\'')
    {
// apostrophed argument
    do {
      c = cget();
      if (c == EOF)
        return nil;  // unterminated!
    } while (c != '\'');
    return UTF8STR(ap + 1, this->cp - ap - 2);
    }
  if (!this->acceptHTML)
    ;  // strict XML requires quoting (?)
  while (!isspace(c) && c != '>' && c != '/' && c != '?' && c != '=' &&c != EOF)
    c = cget();
  this->cp--;  // go back to terminating character
  return UTF8STR(ap, this->cp - ap);
}

- (BOOL) parse
{
// read XML (or HTML) file
  const unsigned char *vp = this->cp;  // value pointer
  int c;

  if (!this->acceptHTML
    && (this->cend - this->cp < 6
      || strncmp((char *)this->cp, "<?xml ", 6) != 0))
    {
      // not a valid XML document start
      return [self _parseError: @"missing <?xml > preamble"];
    }
  c = cget();  // get first character
  while (!this->abort)
    {
#if EXTRA_DEBUG
    NSLog(@"_nextelement %02x %c", c, isprint(c)?c: ' ');
#endif
      switch(c)
        {
          case '\r': 
            this->column = 0;
            break;

          case '\n': 
            this->line++;
            this->column = 0;

          case EOF: 
          case '<': 
          case '&': 
            {
              /* push out any characters that have been collected so far
               */
              if (this->cp - vp > 1)
                {
                  /* check for whitespace only - might set/reset
                   * a flag to indicate so
                   */
                  if ([_del respondsToSelector:
                    @selector(parser:foundCharacters:)])
                    {
                      [_del parser: self foundCharacters:
                        UTF8STR(vp, this->cp - vp - 1)];
                    }
                  vp = this->cp;
                }
            }
        }

      switch(c)
        {
          default: 
            c = cget();  // just collect until we push out (again)
            continue;

          case EOF:
            {
              if ([this->tagPath count] != 0)
                {
                  if (!this->acceptHTML)
                    {
                      /* strict XML nesting error
                       */
                      return [self _parseError: @"unexpected end of file"];
                    }
                while ([this->tagPath count] > 0)
                  {
                    // lazily close all open tags
                  if ([_del respondsToSelector:
                  @selector(parser:didEndElement:namespaceURI:qualifiedName:)])
                      {
                        [_del parser: self
                          didEndElement: [this->tagPath lastObject]
                          namespaceURI: nil qualifiedName: nil];
                      }
                    [this->tagPath removeLastObject];  // pop from stack
                  }
                }
#if EXTRA_DEBUG
              NSLog(@"parserDidEndDocument: ");
#endif
              
              if ([_del respondsToSelector: @selector(parserDidEndDocument: )])
                {
                  [_del parserDidEndDocument: self];
                }
              return YES;
            }

          case '&': 
            {
              NSString  *entity = [self _entity];

              if (!entity)
                {
                  return [self _parseError: @"empty entity name"];
                }
              if ([_del respondsToSelector: @selector(parser:foundCharacters:)])
                {
                  [_del parser: self foundCharacters: entity];
                }
              vp = this->cp;  // next value sequence starts here
              c = cget();  // first character behind ;
              continue;
            }

          case '<': 
            {
              NSString                  *tag;
              NSMutableDictionary       *parameters;
              NSString                  *arg;
              const unsigned char       *tp = this->cp;  // tag pointer

              if (this->cp < this->cend-3
                && strncmp((char *)this->cp, "!--", 3) == 0)
                {
                  /* start of comment skip all characters until "-->"
                   */
                  this->cp += 3;
                  while (this->cp < this->cend-3
                    && strncmp((char *)this->cp, "-->", 3) != 0)
                    {
                      this->cp++;  // search
                    }
                  /* if _del responds to parser: foundComment: 
                   * convert to string (tp+4 ... cp)
                   */
                  this->cp+=3;    // might go beyond cend but does not care
                  vp = this->cp;    // value might continue
                  c = cget();  // get first character behind comment
                  continue;
                }
              c = cget(); // get first character of tag
              if (c == '/')
                {
                  c = cget(); // closing tag </tag begins
                }
              else if (c == '?')
                {
                  /* special tag <?tag begins
                   */
                  c = cget();  // include in tag string
                  //  NSLog(@"special tag <? found");
                  /* FIXME: this->should process this tag in a special
                   * way so that e.g. <?php any PHP script ?> is read
                   * as a single tag!
                   * to do this properly, we need a notion of comments
                   * and quoted string constants...
                   */
                }
              while (c != EOF && !isspace(c)
                && c != '>' && c != '/'  && c != '?')
                {
                  c = cget(); // scan tag until we find a delimiting character
                }
              if (*tp == '/')
                {
                  tag = UTF8STR(tp + 1, this->cp - tp - 2);
                }
              else
                {
                  tag = UTF8STR(tp, this->cp - tp - 1);
                }
#if EXTRA_DEBUG
              NSLog(@"tag=%@ - %02x %c", tag, c, isprint(c)?c: ' ');
#endif
              parameters = [NSMutableDictionary dictionaryWithCapacity: 5];
              while (isspace(c))
                {
                  c = cget();
                }
              while (c != EOF)
                {
                  if (c == '/' && *tp != '/')
                    {
                      // appears to be a />
                      c = cget();
                      if (c != '>')
                        {
                          return [self _parseError: @"<tag/ is missing the >"];
                        }
                      [self _processTag: tag
                                  isEnd: NO
                         withAttributes: parameters];
                      [self _processTag: tag isEnd: YES withAttributes: nil];
                      break;
                    }

                  if (c == '?' && *tp == '?')
                    {
                      // appears to be a ?>
                      c = cget();
                      if (c != '>')
                        {
                          return [self _parseError:
                            @"<?tag ...? is missing the >"];
                        }
                      // process
                      [self _processTag: tag
                                  isEnd: NO
                         withAttributes: parameters];  // single <?tag ...?>
                      break; // done
                    }
                  // this should also allow for line break and tab
                  while (isspace(c))
                    {
                      c = cget();
                    }
                  if (c == '>')
                    {
                      [self _processTag: tag
                                  isEnd: (*tp == '/')
                         withAttributes: parameters];
                      break;
                    }
                  /* get next argument (eats up to /, ?, >, =, space)
                   */
                  arg = [self _qarg];
#if EXTRA_DEBUG
                  NSLog(@"arg=%@", arg);
#endif
                  if (!this->acceptHTML && [arg length] == 0)
                    {
                      return [self _parseError: @"empty attribute name"];
                    }
                  c = cget();  // get delimiting character
                  if (c == '=')
                    {
                      // explicit assignment
                      c = cget();  // skip =
                      [parameters setObject: [self _qarg] forKey: arg];
                      c = cget();  // get character behind qarg value
                    }
                  else  // implicit
                    {
                      [parameters setObject: @"" forKey: arg];
                    }
                }
              vp = this->cp;    // prepare for next value
              c = cget();  // skip > and fetch next character
            }
        }
    }
  return [self _parseError: @"this->aborted"];  // this->aborted
}

- (BOOL) acceptsHTML
{
  return this->acceptHTML;
}

- (BOOL) shouldProcessNamespaces
{
  return this->shouldProcessNamespaces;
}

- (BOOL) shouldReportNamespacePrefixes
{
  return this->shouldReportNamespacePrefixes;
}

- (BOOL) shouldResolveExternalEntities
{
  return this->shouldResolveExternalEntities;
}

- (void) setShouldProcessNamespaces: (BOOL)aFlag
{
  this->shouldProcessNamespaces = aFlag;
}

- (void) setShouldReportNamespacePrefixes: (BOOL)aFlag
{
  this->shouldReportNamespacePrefixes = aFlag;
}

- (void) setShouldResolveExternalEntities: (BOOL)aFlag
{
  this->shouldProcessNamespaces = aFlag;
}

- (void) _setAcceptHTML: (BOOL) flag
{
  this->acceptHTML = flag;
}

- (NSString *) publicID
{
  return [self notImplemented: _cmd];
}

- (NSString *) systemID
{
  return [self notImplemented: _cmd];
}

@end

@implementation NSObject (NSXMLParserDelegateEventAdditions)
- (NSData*) parser: (NSXMLParser*)aParser
  resolveExternalEntityName: (NSString*)aName
  systemID: (NSString*)aSystemID
{
  return nil;
}

- (void) parser: (NSXMLParser*)aParser
  didEndElement: (NSString*)anElementName
  namespaceURI: (NSString*)aNamespaceURI
  qualifiedName: (NSString*)aQualifierName
{
}

- (void) parser: (NSXMLParser*)aParser
  didEndMappingPrefix: (NSString*)aPrefix
{
}

- (void) parser: (NSXMLParser*)aParser
  didStartElement: (NSString*)anElementName
  namespaceURI: (NSString*)aNamespaceURI
  qualifiedName: (NSString*)aQualifierName
  attributes: (NSDictionary*)anAttributeDict
{
}

- (void) parser: (NSXMLParser*)aParser
  didStartMappingPrefix: (NSString*)aPrefix
  toURI: (NSString*)aNamespaceURI
{
}

- (void) parser: (NSXMLParser*)aParser
  foundAttributeDeclarationWithName: (NSString*)anAttributeName
  forElement: (NSString*)anElementName
  type: (NSString*)aType
  defaultValue: (NSString*)aDefaultValue
{
}

- (void) parser: (NSXMLParser*)aParser
  foundCDATA: (NSData*)aBlock
{
}

- (void) parser: (NSXMLParser*)aParser
  foundCharacters: (NSString*)aString
{
}

- (void) parser: (NSXMLParser*)aParser
  foundComment: (NSString*)aComment
{
}

- (void) parser: (NSXMLParser*)aParser
  foundElementDeclarationWithName: (NSString*)anElementName
  model: (NSString*)aModel
{
}

- (void) parser: (NSXMLParser*)aParser
  foundExternalEntityDeclarationWithName: (NSString*)aName
  publicID: (NSString*)aPublicID
  systemID: (NSString*)aSystemID
{
}

- (void) parser: (NSXMLParser*)aParser
  foundIgnorableWhitespace: (NSString*)aWhitespaceString
{
}

- (void) parser: (NSXMLParser*)aParser
  foundInternalEntityDeclarationWithName: (NSString*)aName
  value: (NSString*)aValue
{
}

- (void) parser: (NSXMLParser*)aParser
  foundNotationDeclarationWithName: (NSString*)aName
  publicID: (NSString*)aPublicID
  systemID: (NSString*)aSystemID
{
}

- (void) parser: (NSXMLParser*)aParser
  foundProcessingInstructionWithTarget: (NSString*)aTarget
  data: (NSString*)aData
{
}

- (void) parser: (NSXMLParser*)aParser
  foundUnparsedEntityDeclarationWithName: (NSString*)aName
  publicID: (NSString*)aPublicID
  systemID: (NSString*)aSystemID
  notationName: (NSString*)aNotationName
{
}

- (void) parser: (NSXMLParser*)aParser
  parseErrorOccurred: (NSError*)anError
{
}

- (void) parser: (NSXMLParser*)aParser
  validationErrorOccurred: (NSError*)anError
{
}

- (void) parserDidEndDocument: (NSXMLParser*)aParser
{
}

- (void) parserDidStartDocument: (NSXMLParser*)aParser
{
}

@end

