module m_wxml_core

#ifndef DUMMYLIB
  use m_common_attrs, only: dictionary_t, getLength, get_key, get_value, &
    hasKey, add_item_to_dict, init_dict, reset_dict, destroy_dict, &
    getWhitespaceHandling
  use m_common_array_str, only: vs_str, str_vs, vs_str_alloc
  use m_common_buffer, only: buffer_t, len, add_to_buffer, reset_buffer, &
    dump_buffer
  use m_common_charset, only: XML1_0, XML1_1, checkChars
  use m_common_elstack, only: elstack_t, len, get_top_elstack, pop_elstack, &
    is_empty, init_elstack, push_elstack, destroy_elstack
  use m_common_entities, only: existing_entity, is_unparsed_entity
  use m_common_error, only: FoX_warning_base, FoX_error_base, FoX_fatal_base
  use m_common_io, only: get_unit
  use m_common_namecheck, only: checkEncName, checkName, checkPITarget, &
    checkCharacterEntityReference, checkPublicId, checkSystemId, &
    checkQName, prefixOfQName, localpartofQName, checkPEDef, checkPseudoAttValue, &
    checkAttValue
  use m_common_namespaces, only: namespaceDictionary, getnamespaceURI, &
  initnamespaceDictionary, destroynamespaceDictionary, addDefaultNS, &
  addPrefixedNS, isPrefixInForce, checkNamespacesWriting, checkEndNamespaces
  use m_common_notations, only: add_notation, notation_exists
  use m_common_struct, only: xml_doc_state, init_xml_doc_state, destroy_xml_doc_state, &
    register_internal_PE, register_external_PE, register_internal_GE, register_external_GE
  use m_wxml_escape, only: escape_string

  use pxf, only: pxfabort
#endif

  implicit none
  private

#ifndef DUMMYLIB
  integer, parameter :: indent_inc = 2
  ! TOHW should we let this be set?

  !Output State Machines
  ! status wrt root element:
  integer, parameter :: WXML_STATE_1_JUST_OPENED = 0 
  !File is just opened, nothing written to it yet. 
  integer, parameter :: WXML_STATE_1_BEFORE_ROOT = 1
  !File has been opened, something has been written, but no root element yet.
  integer, parameter :: WXML_STATE_1_DURING_ROOT = 2
  !The root element has been opened but not closed
  integer, parameter :: WXML_STATE_1_AFTER_ROOT = 3
  !The root element has been opened but not closed

  ! status wrt tags:
  integer, parameter :: WXML_STATE_2_OUTSIDE_TAG = 0
  !We are not within a tag.
  integer, parameter :: WXML_STATE_2_INSIDE_PI = 1
  !We are inside a Processing Instruction tag
  integer, parameter :: WXML_STATE_2_INSIDE_ELEMENT = 2
  !We are inside an element tag.
  integer, parameter :: WXML_STATE_2_IN_CHARDATA = 3
  !We are inside deliberately-constructed text. (this is only necessary for preserve_whitespace)

  ! status wrt DTD
  integer, parameter :: WXML_STATE_3_BEFORE_DTD = 0
  ! No DTD has been encountered yet.
  integer, parameter :: WXML_STATE_3_DURING_DTD = 1
  ! Halfway throught outputting a DTD
  integer, parameter :: WXML_STATE_3_INSIDE_INTSUBSET = 2
  !We are inside the internal subset definition
  integer, parameter :: WXML_STATE_3_AFTER_DTD = 3
  ! Finished outputting a DTD
#endif


  type xmlf_t
    private
#ifdef DUMMYLIB
    integer :: i
#else
    type(xml_doc_state) :: xds
    integer                   :: lun = -1
    type(buffer_t)            :: buffer
    type(elstack_t)           :: stack
    type(dictionary_t)        :: dict
    integer                   :: state_1 = -1
    integer                   :: state_2 = -1
    integer                   :: state_3 = -1
    logical                   :: preserve_whitespace
    integer                   :: indent = 0
    character, pointer        :: name(:)
    type(namespaceDictionary) :: nsDict
#endif
  end type xmlf_t

  public :: xmlf_t

  public :: xml_OpenFile
  public :: xml_NewElement
  public :: xml_EndElement
  public :: xml_Close
  public :: xml_AddXMLDeclaration
  public :: xml_AddXMLStylesheet
  public :: xml_AddXMLPI
  public :: xml_AddComment
  public :: xml_AddCharacters
  public :: xml_AddNewline
  public :: xml_AddEntityReference
  public :: xml_AddAttribute
  public :: xml_AddPseudoAttribute
  public :: xml_DeclareNamespace
  public :: xml_UnDeclareNamespace
  public :: xml_AddDOCTYPE
  public :: xml_AddParameterEntity
  public :: xml_AddInternalEntity
  public :: xml_AddExternalEntity
  public :: xml_AddNotation
  public :: xml_AddElementToDTD
  public :: xml_AddAttlistToDTD
  public :: xml_AddPEreferenceToDTD

  public :: xmlf_Name
  public :: xmlf_OpenTag

  interface xml_AddCharacters
    module procedure xml_AddCharacters_Ch
  end interface
  interface xml_AddAttribute
    module procedure xml_AddAttribute_Ch
  end interface
  interface xml_AddPseudoAttribute
    module procedure xml_AddPseudoAttribute_Ch
  end interface
 
#ifndef DUMMYLIB
  !overload error handlers to allow file info
  interface wxml_warning
    module procedure wxml_warning_xf, FoX_warning_base
  end interface
  interface wxml_error
    module procedure wxml_error_xf, FoX_error_base
  end interface
  interface wxml_fatal
    module procedure wxml_fatal_xf, FoX_fatal_base
  end interface

  ! Heuristic (approximate) target for justification of output
  ! only gets used for outputting attributes
  integer, parameter  :: COLUMNS = 80

  ! TOHW - This is the longest string that may be output without
  ! a newline. The buffer must not be larger than this, but its size 
  ! can be tuned for performance.
  !lowest value found so far is 4096, for NAG. We use 1024 just in case.
  integer, parameter  :: xml_recl = 1024
#endif

contains

  subroutine xml_OpenFile(filename, xf, unit, iostat, preserve_whitespace, broken_indenting, replace, addDecl, warning, valid)
    character(len=*), intent(in)  :: filename
    type(xmlf_t), intent(inout)   :: xf
    integer, intent(in), optional :: unit
    integer, intent(out), optional :: iostat
    logical, intent(in), optional :: preserve_whitespace
    logical, intent(in), optional :: broken_indenting
    logical, intent(in), optional :: replace
    logical, intent(in), optional :: addDecl
    logical, intent(in), optional :: warning
    logical, intent(in), optional :: valid
    
#ifndef DUMMYLIB
    logical :: repl, decl
    integer :: iostat_

    if (xf%lun /= -1) &
      call wxml_fatal("Trying to reopen an already-open XML file")
    
    if (present(replace)) then
      repl = replace
    else
      repl = .true.
    endif
    if (present(addDecl)) then
      decl = addDecl
    else
      decl = .true.
    endif
    if (present(iostat)) iostat = 0

    allocate(xf%name(0))
    
    if (present(unit)) then
      if (unit==-1) then
        call get_unit(xf%lun,iostat_)
        if (iostat_ /= 0) then
          if (present(iostat)) iostat = iostat_
          return
        endif
      else
        xf%lun = unit
      endif
    else
      call get_unit(xf%lun,iostat_)
      if (iostat_ /= 0) then
        if (present(iostat)) iostat = iostat_
        return
      endif
    endif
    
    ! Use large I/O buffer in case the O.S./Compiler combination
    ! has hard-limits by default (i.e., NAGWare f95's 1024 byte limit)
    ! This is related to the maximum size of the buffer.
    ! TOHW - This is the longest string that may be output without
    ! a newline. The buffer must not be larger than this, but its size 
    ! can be tuned for performance.
    
    if (repl) then
      ! NAG insists on unnecessary duplication of iostat etc here
      if (present(iostat)) then
        open(unit=xf%lun, file=filename, form="formatted", status="replace", &
          action="write", recl=xml_recl, iostat=iostat)
      else	
        open(unit=xf%lun, file=filename, form="formatted", status="replace", &
          action="write", recl=xml_recl)
      endif
    else 
      if (present(iostat)) then
        open(unit=xf%lun, file=filename, form="formatted", status="new", &
          action="write", recl=xml_recl, iostat=iostat)
      else	
        open(unit=xf%lun, file=filename, form="formatted", status="new", &
          action="write", recl=xml_recl)
      endif
    endif

    call init_elstack(xf%stack)

    call init_dict(xf%dict)
    !NB it can make no difference which XML version we are using
    !until after we output the XML declaration. So we set it to
    !1.0 for the moment & reset below.
    ! Actually, this is done automatically in initializing xf%xds
    call init_xml_doc_state(xf%xds)
    xf%xds%documentURI => vs_str_alloc(filename)

    if (present(warning)) xf%xds%warning = warning
    if (present(valid)) xf%xds%valid = valid 
    
    xf%state_1 = WXML_STATE_1_JUST_OPENED
    xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    xf%state_3 = WXML_STATE_3_BEFORE_DTD
    
    if (present(preserve_whitespace)) then
      xf%preserve_whitespace = preserve_whitespace
    elseif (present(broken_indenting)) then
      xf%preserve_whitespace = .not.broken_indenting
    else
      xf%preserve_whitespace = .false.
    endif
      
    xf%indent = 0
    
    if (decl) then
      call xml_AddXMLDeclaration(xf,encoding='UTF-8')
      ! which will reset the buffer itself
    else
      call reset_buffer(xf%buffer, xf%lun, xf%xds%xml_version)
    endif
    
    call initNamespaceDictionary(xf%nsDict)
#endif
  end subroutine xml_OpenFile


  subroutine xml_AddXMLDeclaration(xf, version, encoding, standalone)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in), optional :: version
    character(len=*), intent(in), optional :: encoding
    logical, intent(in), optional :: standalone

#ifndef DUMMYLIB
    call check_xf(xf)
    ! Don't need to call checkChars on args, everything is checked
    ! fully below anyway.
    
    if (xf%state_1 /= WXML_STATE_1_JUST_OPENED) &
      call wxml_error("Tried to put XML declaration in wrong place")
    
    call reset_buffer(xf%buffer, xf%lun, xf%xds%xml_version)
    
    call xml_AddXMLPI(xf, "xml", xml=.true.)
    if (present(version)) then
      if (version =="1.0") then
        xf%xds%xml_version = XML1_0
        call xml_AddPseudoAttribute(xf, "version", version)
      elseif (version=="1.1") then
        xf%xds%xml_version = XML1_1
        call xml_AddPseudoAttribute(xf, "version", version)
      else
        call wxml_error("Invalid XML version.")
      endif
    else
      call xml_AddPseudoAttribute(xf, "version", "1.0")
      xf%xds%xml_version = XML1_0
    endif
    if (present(encoding)) then
      if (.not.checkEncName(encoding)) &
        call wxml_error("Invalid encoding name: "//encoding)
      if (encoding /= 'UTF-8' .and. encoding /= 'utf-8') &
        call wxml_warning(xf, "Non-default encoding specified: "//encoding)
      call xml_AddPseudoAttribute(xf, "encoding", encoding)
    endif
    if (present(standalone)) then
      xf%xds%standalone_declared = .true.
      xf%xds%standalone = standalone
      if (standalone) then
        call xml_AddPseudoAttribute(xf, "standalone", "yes")
      else
        call xml_AddPseudoAttribute(xf, "standalone", "no")
      endif
    endif
    call close_start_tag(xf)
    ! We have to close explicitly here to ensure nothing gets tied
    ! up in the XML declaration
    xf%state_1 = WXML_STATE_1_BEFORE_ROOT
#endif
  end subroutine xml_AddXMLDeclaration


  subroutine xml_AddDOCTYPE(xf, name, system, public)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in), optional :: system, public
#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddDOCTYPE: Invalid character in name")
    if (present(system)) then
      if (.not.checkChars(system,xf%xds%xml_version)) call wxml_error("xml_AddDOCTYPE: Invalid character in system")
    endif
    if (present(public)) then
      if (.not.checkChars(public,xf%xds%xml_version)) call wxml_error("xml_AddDOCTYPE: Invalid character in public")
    endif
    
    if (present(public).and..not.present(system)) &
      call wxml_error('xml_AddDOCTYPE: PUBLIC supplied without SYSTEM for: '//name)

    ! By having an external ID we render this non-standalone (unless we've said that it is in the declaration)
    if (present(system).and..not.xf%xds%standalone_declared) &
      xf%xds%standalone=.false.

    call close_start_tag(xf)
    
    if (xf%state_1 /= WXML_STATE_1_BEFORE_ROOT) &
      call wxml_error("Tried to put XML DOCTYPE in wrong place: "//name)

    if (xf%state_3 /= WXML_STATE_3_BEFORE_DTD) then
      call wxml_error("Tried to output more than one DOCTYPE declaration: "//name)
    else
      xf%state_3 = WXML_STATE_3_DURING_DTD
    endif

    if (.not.checkName(name, xf%xds)) &
         call wxml_error("Invalid Name in DTD "//name)
    
    call add_eol(xf)
    call add_to_buffer("<!DOCTYPE "//name, xf%buffer, .false.)

    deallocate(xf%name)
    allocate(xf%name(len(name)))
    xf%name = vs_str(name)

    if (present(system)) then
      if (present(public)) then
        if (.not.checkPublicId(public)) &
          call wxml_error("Invalid PUBLIC ID "//public)
        call add_to_buffer(' PUBLIC', xf%buffer, .false.)
        call add_to_buffer(' "'//public//'"', xf%buffer, .true.)
      else
        call add_to_buffer(' SYSTEM', xf%buffer, .false.)
      endif
      if (scan(system, "'")/=0) then
        if (scan(system, '"')/=0) &
          call wxml_error("Invalid SYSTEM ID "//system)
        call add_to_buffer(' "'//system//'"', xf%buffer, .true.)
      else
        call add_to_buffer(" '"//system//"'", xf%buffer, .true.)
      endif
    endif
#endif
  end subroutine xml_AddDOCTYPE


  subroutine xml_AddParameterEntity(xf, name, PEdef, system, public)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in), optional :: PEDef
    character(len=*), intent(in), optional :: system
    character(len=*), intent(in), optional :: public
#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddParameterEntity: Invalid character in name")
    if (present(PEDef)) then
      if (.not.checkChars(PEDef,xf%xds%xml_version)) call wxml_error("xml_AddParameterEntity: Invalid character in PEDef")
    endif
    if (present(system)) then
      if (.not.checkChars(system,xf%xds%xml_version)) call wxml_error("xml_AddParameterEntity: Invalid character in system")
    endif
    if (present(public)) then
      if (.not.checkChars(public,xf%xds%xml_version)) call wxml_error("xml_AddParameterEntity: Invalid character in public")
    endif
    
    ! By adding a parameter entity (internal or external) we make this
    ! a non-standalone document.
    if (.not.xf%xds%standalone_declared) &
      xf%xds%standalone = .false.

    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot define Parameter Entity here: "//name)
      
    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    if (present(PEdef)) then
      if (present(system) .or. present(public)) &
        call wxml_fatal("Parameter entity "//name//" cannot have both a PEdef and an External ID")
    else
      if (.not.present(system)) &
        call wxml_fatal("Parameter entity "//name//" must have either a PEdef or an External ID")
    endif
    if (present(PEdef)) then
      if (.not.checkPEDef(PEDef, xf%xds)) &
        call wxml_fatal("Parameter entity definition is invalid: "//PEDef)
      if (xf%xds%standalone) then
        if (.not.checkExistingRefs()) &
          call wxml_error("Tried to reference unregistered parameter entity")
      else
        if (.not.checkExistingRefs()) &
          call wxml_warning(xf, "Reference to unknown parameter entity")
      endif
      call register_internal_PE(xf%xds, name, PEdef)
    else
      if (.not.checkSystemID(system)) &
        call wxml_fatal("Parameter entity System ID is invalid: "//system)
      if (present(public)) then
        if (.not.checkPublicID(public)) &
          call wxml_fatal("Parameter entity Public ID is invalid: "//public)
      endif
      call register_external_PE(xf%xds, name, system, public)
    endif

    call add_eol(xf)

    call add_to_buffer('<!ENTITY % '//name, xf%buffer, .false.) ! name can never contain whitespace
    if (present(PEdef)) then
      if (index(PEdef, '"') > 0) then
        call add_to_buffer(" '"//PEdef//"'", xf%buffer, .true.)
      else
        call add_to_buffer(' "'//PEdef//'"', xf%buffer, .true.)
      endif
        call add_to_buffer('>', xf%buffer, .false.)
    else
      if (present(public)) then
        call add_to_buffer(' PUBLIC', xf%buffer, .false.)
        call add_to_buffer(' "'//public//'"', xf%buffer, .true.)
      else
        call add_to_buffer(' SYSTEM', xf%buffer, .false.)
      endif
      if (scan(system, "'")/=0) then
        if (scan(system, '"')/=0) &
          call wxml_error("Invalid SYSTEM ID "//system)
        call add_to_buffer(' "'//system//'"', xf%buffer, .true.)
      else
        call add_to_buffer(" '"//system//"'", xf%buffer, .true.)
      endif
      call add_to_buffer(">", xf%buffer)
    endif

  contains
    function checkExistingRefs() result(p)
      logical :: p
      
      integer :: i1, i2
      
      ! Here we assume we have syntactic well-formedness as
      ! checked by checkPEDef.
      
      p = .false.
      i1 = index(PEDef, '%')
      i2 = 0 
      do while (i1 > 0)
        i1 = i2 + i1
        i2 = index(PEDef(i1+1:),';')
        if (i2 == 0) return
        i2 = i1 + i2
        if (.not.existing_entity(xf%xds%PEList, PEDef(i1+1:i2-1))) &
          return
        i1 = index(PEDef(i2+1:), '%')
      enddo
      p = .true.
      
    end function checkExistingRefs
#endif
  end subroutine xml_AddParameterEntity


  subroutine xml_AddInternalEntity(xf, name, value)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in) :: value

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddInternalEntity: Invalid character in name")
    if (.not.checkChars(value,xf%xds%xml_version)) call wxml_error("xml_AddInternalEntity: Invalid character in value")
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot define Entity here: "//name)
      
    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    if (.not.checkName(name, xf%xds)) &
      call wxml_error("xml_AddInternalEntity: Invalid Name: "//name)
    call register_internal_GE(xf%xds, name, value)

    call add_eol(xf)
    
    !FIXME - valid entity values?
    call add_to_buffer('<!ENTITY '//name//' ', xf%buffer, .false.) ! name cannot contain whitespace
    if (index(value, '"') > 0) then
      call add_to_buffer("'"//value//"'>", xf%buffer, .true.)
    else
      call add_to_buffer('"'//value//'">', xf%buffer, .true.)
    endif
#endif
  end subroutine xml_AddInternalEntity


  subroutine xml_AddExternalEntity(xf, name, system, public, notation)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in) :: system
    character(len=*), intent(in), optional :: public
    character(len=*), intent(in), optional :: notation

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddExternalEntity: Invalid character in name")
    if (.not.checkChars(system,xf%xds%xml_version)) call wxml_error("xml_AddExternalEntity: Invalid character in system")
    if (present(public)) then
      if (.not.checkChars(public,xf%xds%xml_version)) call wxml_error("xml_AddExternalEntity: Invalid character in public")
    endif
    if (present(notation)) then
      if (.not.checkChars(notation,xf%xds%xml_version)) call wxml_error("xml_AddExternalEntity: Invalid character in notation")
    endif
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot define Entity here: "//name)

    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    if (present(notation)) then
      if (.not.notation_exists(xf%xds%nList, notation)) then
        if (.not.xf%xds%standalone) then
          call wxml_warning(xf, "Tried to add possibly unregistered notation to entity: "//name)
          if (.not.checkName(notation, xf%xds)) &
            call wxml_error("xml_AddExternalEntity: Invalid notation "//notation)
        else
          call wxml_error("Tried to add non-existent notation to entity: "//name)
        endif
      endif
    endif
  
    if (.not.checkName(name, xf%xds)) &
      call wxml_error("xml_AddExternalEntity: Invalid Name: "//name)
    if (.not.checkSystemID(system)) &
      call wxml_error("xml_AddExternalEntity: Invalid System: "//system)
    if (present(public)) then
      if (.not.checkPublicID(public)) &
        call wxml_error("xml_AddExternalEntity: Invalid Public: "//public)
    endif
    ! Notation only needs checked if not already registered - done above.
    call register_external_GE(xf%xds, name, system, public, notation)
    
    call add_eol(xf)
    
    call add_to_buffer('<!ENTITY '//name, xf%buffer, .false.)
    if (present(public)) then
      call add_to_buffer(" PUBLIC", xf%buffer, .false.)
      call add_to_buffer(' "'//public//'"', xf%buffer, .true.)
    else
      call add_to_buffer(' SYSTEM', xf%buffer, .false.)
    endif
    if (scan(system, "'")/=0) then
      if (scan(system, '"')/=0) &
        call wxml_error("Invalid SYSTEM ID "//system)
      call add_to_buffer(' "'//system//'"', xf%buffer, .true.)
    else
      call add_to_buffer(" '"//system//"'", xf%buffer, .true.)
    endif
    if (present(notation)) then
      ! FIXME Has notation been declared yet?
      call add_to_buffer(' NDATA '//notation, xf%buffer, .false.) ! notation cannot contain whitespace
    endif
    call add_to_buffer('>', xf%buffer, .false.)
#endif
  end subroutine xml_AddExternalEntity


  subroutine xml_AddNotation(xf, name, system, public)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in), optional :: system
    character(len=*), intent(in), optional :: public

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddNotation: Invalid character in name")
    if (present(system)) then
      if (.not.checkChars(system,xf%xds%xml_version)) call wxml_error("xml_AddNotation: Invalid character in system")
    endif
    if (present(public)) then
      if (.not.checkChars(public,xf%xds%xml_version)) call wxml_error("xml_AddNotation: Invalid character in public")
    endif
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot define Notation here: "//name)
    
    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    if (notation_exists(xf%xds%nList, name)) &
      call wxml_error("Tried to create duplicate notation: "//name)
    
    call add_eol(xf)

    if (.not.checkName(name, xf%xds)) &
      call wxml_error("Notation name is illegal in xml_AddNotation: "//name)
    if (present(system)) then
      if (.not.checkSystemId(system)) &
        call wxml_error("System ID name is illegal in xml_AddNotation: "//system)
    endif
    if (present(public)) then
      if (.not.checkPublicId(public)) &
        call wxml_error("Public ID name is illegal in xml_AddNotation: "//public)
    endif
    
    call add_notation(xf%xds%nList, name, system, public)
    call add_to_buffer('<!NOTATION '//name, xf%buffer, .false.)
    if (present(public)) then
      call add_to_buffer(" PUBLIC", xf%buffer, .false.)
      call add_to_buffer(' "'//public//'"', xf%buffer, .true.)
    elseif (present(system)) then
      call add_to_buffer(' SYSTEM', xf%buffer, .false.)
    endif
    if (present(system)) then
      if (index(system, '"') > 0) then
        call add_to_buffer(' "'//system//'"', xf%buffer, .true.)
      else
        call add_to_buffer(" '"//system//"'", xf%buffer, .true.)
      endif
    endif
    call add_to_buffer('>', xf%buffer, .false.)
#endif
  end subroutine xml_AddNotation


  subroutine xml_AddElementToDTD(xf, name, declaration)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in) :: declaration

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddElementToDTD: Invalid character in name")
    if (.not.checkChars(declaration,xf%xds%xml_version)) call wxml_error("xml_AddElementToDTD: Invalid character in declaration")

    if (.not.checkName(name, xf%xds)) &
      call wxml_error("Element name is illegal in xml_AddElementToDTD: "//name)

    !FIXME we should check declaration syntax too.
    call wxml_warning(xf, "Adding ELEMENT declaration to DTD. Cannot guarantee well-formedness")
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot write to DTD here: xml_AddElementToDTD")

    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    call add_eol(xf)
    call add_to_buffer('<!ELEMENT '//name//' '//declaration//'>', xf%buffer, .false.)
#endif
  end subroutine xml_AddElementToDTD


  subroutine xml_AddAttlistToDTD(xf, name, declaration)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name
    character(len=*), intent(in) :: declaration

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddAttListToDTD: Invalid character in name")
    if (.not.checkChars(declaration,xf%xds%xml_version)) call wxml_error("xml_AddAttListToDTD: Invalid character in declaration")

    if (.not.checkName(name, xf%xds)) &
      call wxml_error("Attlist name is illegal in xml_AddAttlistToDTD: "//name)

    !FIXME we should check declaration syntax too.
    call wxml_warning(xf, "Adding ATTLIST declaration to DTD. Cannot guarantee well-formedness")
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot write to DTD here: xml_AddAttlistToDTD")

    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    call add_eol(xf)
    call add_to_buffer('<!ATTLIST '//name//' '//declaration//'>', xf%buffer, .false.)
#endif
  end subroutine xml_AddAttlistToDTD
    

  subroutine xml_AddPEReferenceToDTD(xf, name)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: name

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddPEReferenceToDTD: Invalid character in name")

    if (.not.checkName(name, xf%xds)) &
      call wxml_error("Trying to add illegal name in xml_AddPEReferenceToDTD: "//name)

    call wxml_warning(xf, "Adding PEReference to DTD. Cannot guarantee well-formedness")
    if (.not.existing_entity(xf%xds%PEList, name)) then
      if (.not.xf%xds%standalone) then
        call wxml_warning(xf, "Tried to reference possibly unregistered parameter entity in DTD: "//name)
      else
        call wxml_error("Tried to reference unregistered parameter entity in DTD "//name)
      endif
    else
      if (is_unparsed_entity(xf%xds%PEList, name)) &
        call wxml_error("Tried to reference unparsed parameter entity in DTD "//name)
    endif
    
    if (xf%state_3 == WXML_STATE_3_DURING_DTD) then
      call add_to_buffer(" [", xf%buffer, .false.)
      xf%state_3 = WXML_STATE_3_INSIDE_INTSUBSET
    endif

    if (xf%state_3 /= WXML_STATE_3_INSIDE_INTSUBSET) &
      call wxml_fatal("Cannot write to DTD here: xml_AddPEReferenceToDTD")

    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) then
      call close_start_tag(xf)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    endif

    call add_eol(xf)
    call add_to_buffer('%'//name//';', xf%buffer, .false.)

#endif
  end subroutine xml_AddPEReferenceToDTD


  subroutine xml_AddXMLStylesheet(xf, href, type, title, media, charset, alternate)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in) :: href
    character(len=*), intent(in) :: type
    character(len=*), intent(in), optional :: title
    character(len=*), intent(in), optional :: media
    character(len=*), intent(in), optional :: charset
    logical,          intent(in), optional :: alternate

#ifndef DUMMYLIB
    call check_xf(xf)
    ! Don't bother doing checkChars - all pseudoatts get checked anyway.
    
    if (xf%state_1 /= WXML_STATE_1_JUST_OPENED &
         .and. xf%state_1 /= WXML_STATE_1_BEFORE_ROOT) &
      call wxml_error("Cannot add stylesheet here: "//href)

    call close_start_tag(xf)
    
    call xml_AddXMLPI(xf, 'xml-stylesheet', xml=.true.)
    call xml_AddPseudoAttribute(xf, 'href', href)
    call xml_AddPseudoAttribute(xf, 'type', type)
    
    if (present(title)) call xml_AddPseudoAttribute(xf, 'title', title)
    if (present(media)) call xml_AddPseudoAttribute(xf, 'media', media)
    if (present(charset)) call xml_AddPseudoAttribute(xf, 'charset', charset)
    if (present(alternate)) then
      if (alternate) then
        call xml_AddPseudoAttribute(xf, 'alternate', 'yes')
      else
        call xml_AddPseudoAttribute(xf, 'alternate', 'no')
      endif
    endif
    if (xf%state_1 == WXML_STATE_1_JUST_OPENED) &
         xf%state_1 = WXML_STATE_1_BEFORE_ROOT 
    xf%state_2 = WXML_STATE_2_INSIDE_PI
#endif
  end subroutine xml_AddXMLStylesheet
  

  subroutine xml_AddXMLPI(xf, name, data, xml, ws_significant)
    type(xmlf_t), intent(inout)            :: xf
    character(len=*), intent(in)           :: name
    character(len=*), intent(in), optional :: data
    logical, intent(in), optional :: xml
    logical, intent(in), optional :: ws_significant

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_AddXMLPI: Invalid character in name")
    if (present(data)) then
      if (.not.checkChars(data,xf%xds%xml_version)) call wxml_error("xml_AddXMLPI: Invalid character in data")
    endif
    
    select case (xf%state_1)
    case (WXML_STATE_1_JUST_OPENED) 
      xf%state_1 = WXML_STATE_1_BEFORE_ROOT
    case default
      call close_start_tag(xf)
      call add_eol(xf)
    end select

    if (present(xml)) then
      if (.not.checkName(name, xf%xds)) &
        call wxml_error(xf, "Invalid PI Target "//name)
    else
      if (.not.checkPITarget(name, xf%xds)) &
        call wxml_error(xf, "Invalid PI Target "//name)
    endif
    call add_to_buffer("<?" // name, xf%buffer, .false.)
    if (present(data)) then
      if (index(data, '?>') > 0) &
           call wxml_error(xf, "Tried to output invalid PI data "//data)
      call add_to_buffer(' ', xf%buffer, .false.)
      call add_to_buffer(data//'?>', xf%buffer, ws_significant)
      ! state_2 is now OUTSIDE_TAG from close_start_tag
    else
      xf%state_2 = WXML_STATE_2_INSIDE_PI
      call reset_dict(xf%dict)
    endif
#endif
  end subroutine xml_AddXMLPI


  subroutine xml_AddComment(xf, comment, ws_significant)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in)  :: comment
    logical, intent(in), optional :: ws_significant

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(comment,xf%xds%xml_version)) call wxml_error("xml_AddComment: Invalid character in comment")
    
    select case (xf%state_1)
    case (WXML_STATE_1_JUST_OPENED) 
      xf%state_1 = WXML_STATE_1_BEFORE_ROOT
    case (WXML_STATE_1_BEFORE_ROOT)
      call close_start_tag(xf)
      call add_eol(xf)
    case (WXML_STATE_1_DURING_ROOT)
      call close_start_tag(xf)
      if (.not.xf%preserve_whitespace.and.xf%state_2 == WXML_STATE_2_OUTSIDE_TAG) call add_eol(xf)
    case (WXML_STATE_1_AFTER_ROOT)
      call close_start_tag(xf)
      call add_eol(xf)
    end select

    if (index(comment,'--') > 0 .or. comment(len(comment):) == '-') &
         call wxml_error("Tried to output invalid comment "//comment)

    call add_to_buffer("<!--", xf%buffer, .false.)
    call add_to_buffer(comment, xf%buffer, ws_significant)
    call add_to_buffer("-->", xf%buffer, .false.)
#endif
  end subroutine xml_AddComment


  subroutine xml_NewElement(xf, name)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in)  :: name

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(name,xf%xds%xml_version)) call wxml_error("xml_NewElement: Invalid character in name")
    
    select case (xf%state_1)
    case (WXML_STATE_1_JUST_OPENED, WXML_STATE_1_BEFORE_ROOT)
      if (size(xf%name) > 0) then
        if (str_vs(xf%name) /= name) & 
          call wxml_error(xf, "Root element name does not match DTD: "//name)
      endif
      call close_start_tag(xf)
      if (xf%state_3 /= WXML_STATE_3_BEFORE_DTD) then
        select case (xf%state_3)
        case (WXML_STATE_3_DURING_DTD)
          call add_to_buffer('>', xf%buffer, .false.)
          xf%state_3 = WXML_STATE_3_AFTER_DTD
        case (WXML_STATE_3_INSIDE_INTSUBSET)
          xf%state_3 = WXML_STATE_3_AFTER_DTD
          call add_eol(xf)
          call add_to_buffer(']>', xf%buffer, .false.)
        end select
      endif
      call add_eol(xf)
    case (WXML_STATE_1_DURING_ROOT)
      call close_start_tag(xf)
      if (.not.xf%preserve_whitespace.and.xf%state_2 == WXML_STATE_2_OUTSIDE_TAG) call add_eol(xf)
    case (WXML_STATE_1_AFTER_ROOT)
      call wxml_error(xf, "Two root elements: "//name)
    end select
    
    if (.not.checkQName(name, xf%xds)) then
      call wxml_error(xf, 'Element name '//name//' is not valid')
    endif

    if (len(prefixOfQName(name)) > 0) then
      if (.not.isPrefixInForce(xf%nsDict, prefixOfQName(name))) &
        call wxml_error(xf, "Namespace prefix not registered: "//prefixOfQName(name))
    endif
    
    call push_elstack(name,xf%stack)
    call add_to_buffer("<"//name, xf%buffer, .false.)
    xf%state_2 = WXML_STATE_2_INSIDE_ELEMENT
    call reset_dict(xf%dict)
    xf%indent = xf%indent + indent_inc
    xf%state_1 = WXML_STATE_1_DURING_ROOT
#endif
  end subroutine xml_NewElement
  

  subroutine xml_AddCharacters_ch(xf, chars, parsed, ws_significant)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in)  :: chars
    logical, intent(in), optional :: parsed
    logical, intent(in), optional :: ws_significant

#ifndef DUMMYLIB
    logical :: pc

    call check_xf(xf)
    if (.not.checkChars(chars, xf%xds%xml_version)) call wxml_error("xml_AddCharacters: Invalid character in chars")
    
    if (xf%state_1 /= WXML_STATE_1_DURING_ROOT) &
         call wxml_fatal("Tried to add text section in wrong place: "//chars)
    
    if (present(parsed)) then
      pc = parsed
    else
      pc = .true.
    endif
    
    call close_start_tag(xf)

    if (pc) then
      call add_to_buffer(escape_string(chars, xf%xds%xml_version), xf%buffer, ws_significant)
    else
      if (index(chars,']]>') > 0) &
           call wxml_fatal("Tried to output invalid CDATA: "//chars)
      call add_to_buffer("<![CDATA["//chars//"]]>", xf%buffer, ws_significant)
    endif
    
    xf%state_2 = WXML_STATE_2_IN_CHARDATA
#endif
  end subroutine xml_AddCharacters_Ch


  subroutine xml_AddNewline(xf)
    type(xmlf_t), intent(inout) :: xf

#ifndef DUMMYLIB
    call xml_AddCharacters(xf, "") ! FIXME Does this line do anything?
    call add_eol(xf)
#endif
  end subroutine xml_AddNewline

  
  subroutine xml_AddEntityReference(xf, entityref)
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: entityref

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(entityref, xf%xds%xml_version)) call wxml_error("xml_AddEntityReference: Invalid character in entityref")
    
    !Where can we add this? If we allow the full gamut
    !of entities, we can no longer properly ensure
    !well-formed output, unless we tie the sax parser
    !in as well ...

    call close_start_tag(xf)

    if (xf%state_2 /= WXML_STATE_2_OUTSIDE_TAG .and. &
      xf%state_2 /= WXML_STATE_2_IN_CHARDATA)         &
      call wxml_fatal("Tried to add entity reference in wrong place: "//entityref)

    if (.not.checkCharacterEntityReference(entityref, xf%xds%xml_version)) then
      !it's not just a unicode entity
      call wxml_warning(xf, "Entity reference added - document may not be well-formed")
      if (.not.existing_entity(xf%xds%entityList, entityref)) then
        if (xf%xds%standalone) then
          call wxml_error("Tried to reference unregistered entity")
        else
          call wxml_warning(xf, "Tried to reference unregistered entity")
        endif
      else
        if (is_unparsed_entity(xf%xds%entityList, entityref)) &
          call wxml_error("Tried to reference unparsed entity")
      endif
    endif

    call add_to_buffer('&'//entityref//';', xf%buffer, .false.)
    xf%state_2 = WXML_STATE_2_IN_CHARDATA
#endif
  end subroutine xml_AddEntityReference


  subroutine xml_AddAttribute_Ch(xf, name, value, escape, type, ws_significant)
    type(xmlf_t), intent(inout)             :: xf
    character(len=*), intent(in)            :: name
    character(len=*), intent(in)            :: value
    logical, intent(in), optional           :: escape
    character(len=*), intent(in), optional  :: type
    logical, intent(in), optional           :: ws_significant

#ifndef DUMMYLIB
    logical :: esc
    character, pointer :: type_(:)

    ! if (name=="xml:space") then
    ! The value can only be "default" or "preserve", by 2.10
    ! However, that value can be obscured behind entities,
    ! and we can't check that without full entity expansion
    ! available to the output processor.
    ! FIXME check when we have the ability

    if (present(type)) then
      if (type/='CDATA'.and.type/='ID'.and.type/='IDREF'.and.type/='IDREFS'.and.type/='NMTOKEN'.and.type/='NMTOKENS' &
        .and.type/='ENTITY'.and.type/='ENTITIES'.and.type/='NOTATION') then
        call wxml_fatal("Invalid type in xml_AddAttribute: "//type)
      endif
      type_ => vs_str_alloc(type)
    else
      ! We assume CDATA, but need to worry about whether the caller cares about whitespace ...
      if (present(ws_significant)) then
        if (ws_significant) then
          type_ => vs_str_alloc('CDATA')
        else
          type_ => vs_str_alloc('CDANO') ! CDAta, whitespace Not significant
        endif
      else
        type_ => vs_str_alloc('CDAMB')   ! CDAta, whitespace MayBe significant
      endif
    endif

    call check_xf(xf)
    if (.not.checkChars(name, xf%xds%xml_version)) call wxml_error("xml_AddAttribute: Invalid character in name")
    if (.not.checkChars(value, xf%xds%xml_version)) call wxml_error("xml_AddAttribute: Invalid character in value")
    
    if (present(escape)) then
      esc = escape
    else
      esc = .true.
    endif

    ! FIXME when escape is false we should still verify somehow.
    ! minimal check: only extra allowed is character entity references.
    ! We check they exist, and are not unparsed.
    ! Ideally we would fully expand all entity references (at least for
    ! a standalone document where we can) and then
    ! match the resultant production against [XML]-3.3.1. This is
    ! initially too much work though, so we just check simple
    ! syntactic constraint.

    if (.not.esc) then
      if (.not.checkAttValue(value, xf%xds)) &
        call wxml_error(xf, "Invalid attribute value: "//value)
      if (index(value, '&') > 0) then
        ! There are entity references
        ! They should exist (unless we are not standalone) and they must not be unparsed.
        if (.not.checkExistingRefsInAttValue()) then
          if (xf%xds%standalone) then
            call wxml_error(xf, "outputting unknown entity. Cannot guarantee validity.")
          else
            call wxml_warning(xf, "Warning: outputting unknown entity. Cannot guarantee validity.")
          endif
        endif
        if (.not.checkParsedRefsInAttValue()) &
          call wxml_error(xf, "Warning: outputting unknown entity. Cannot guarantee validity.")
      endif
    endif

    if (xf%state_2 /= WXML_STATE_2_INSIDE_ELEMENT) &
         call wxml_error(xf, "attributes outside element content: "//name)

    if (hasKey(xf%dict,name)) then
      call wxml_error(xf, "duplicate att name: "//name)
    elseif (hasKey(xf%dict, &
      getnamespaceURI(xf%nsDict,prefixOfQname(name)), localpartofQname(name))) then
      call wxml_error(xf, "duplicate att after namespace processing: "//name)
    endif

    if (.not.checkQName(name, xf%xds)) &
         call wxml_error(xf, "invalid attribute name: "//name)

    if (len(prefixOfQName(name))>0) then
      if (prefixOfQName(name)/="xml".and.prefixOfQName(name)/="xmlns") then
        if (.not.isPrefixInForce(xf%nsDict, prefixOfQName(name))) &
          call wxml_error(xf, "namespace prefix not registered: "//prefixOfQName(name))
      endif
      if (esc) then
        call add_item_to_dict(xf%dict, localpartofQname(name), escape_string(value, xf%xds%xml_version), prefixOfQName(name), &
          getnamespaceURI(xf%nsDict,prefixOfQname(name)), type=str_vs(type_))
      else
        call add_item_to_dict(xf%dict, localpartofQname(name), value, prefixOfQName(name), &
          getnamespaceURI(xf%nsDict,prefixOfQName(name)), type=str_vs(type_))
      endif

    else
      if (esc) then
        call add_item_to_dict(xf%dict, name, escape_string(value, xf%xds%xml_version), type=str_vs(type_))
      else
        call add_item_to_dict(xf%dict, name, value, type=str_vs(type_))
      endif
    endif

    !FIXME need to deallocate this when we move to better error handling
    deallocate(type_)

  contains
    function checkExistingRefsInAttValue() result(p)
      logical :: p
      
      integer :: i1, i2
      
      ! Here we assume we have syntactic well-formedness as
      ! checked by checkAttValue.
      ! We also assume we do not have simply one entity as
      ! the contents - that is checked by checkAttValueEntity
      
      p = .false.
      i1 = index(value, '&')
      i2 = 0 
      do while (i1 > 0)
        i1 = i2 + i1
        i2 = index(value(i1+1:),';')
        if (i2 == 0) return
        i2 = i1 + i2
        if (.not.existing_entity(xf%xds%entityList, value(i1+1:i2-1)) .and. &
          .not.checkCharacterEntityReference(value(i1+1:i2-1), xf%xds%xml_version)) &
          return
        i1 = index(value(i2+1:), '&')
      enddo
      p = .true.
      
    end function checkExistingRefsInAttValue
    
    function checkParsedRefsInAttValue() result(p)
      logical :: p
      
      integer :: i1, i2
      
      ! Here we assume we have syntactic well-formedness as
      ! checked by checkAttValue.
      
      p = .false.
      i1 = index(value, '&')
      i2 = 0
      do while (i1 > 0)
        i1 = i1 + i2
        i2 = index(value(i1+1:),';')
        if (i2 == 0) return
        i2  = i1 + i2
        if (is_unparsed_entity(xf%xds%entityList, value(i1+1:i2-1))) &
          return
        i1 = index(value(i2+1:), '&')
      enddo
      p = .true.

    end function checkParsedRefsInAttValue
#endif
  end subroutine xml_AddAttribute_Ch


  subroutine xml_AddPseudoAttribute_Ch(xf, name, value, escape, ws_significant)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in)  :: name
    character(len=*), intent(in)  :: value
    logical, intent(in), optional :: escape
    logical, intent(in), optional :: ws_significant

#ifndef DUMMYLIB
    logical :: esc
    character(len=5) :: type

    call check_xf(xf)
    if (.not.checkChars(name, xf%xds%xml_version)) call wxml_error("xml_AddPseudoAttribute: Invalid character in name")
    if (.not.checkChars(value, xf%xds%xml_version)) call wxml_error("xml_AddPseudoAttribute: Invalid character in value")
    
    if (present(escape)) then
      esc = escape
    else
      esc = .true.
    endif
    if (present(ws_significant)) then
      if (ws_significant) then
        type='CDATA'
      else
        type='CDANO' ! CDAta, whitespace Not significant
      endif
    else
      type='CDAMB'   ! CDAta, whitespace MayBe significant
    endif

    if (index(value, '?>') > 0) &
        call wxml_error(xf, "Invalid pseudo-attribute value: "//value)
    if (.not.esc) then
      if (.not.checkPseudoAttValue(value, xf%xds)) &
        call wxml_error(xf, "Invalid pseudo-attribute value: "//value)
    endif

    if (xf%state_2 /= WXML_STATE_2_INSIDE_PI) &
         call wxml_error("PI pseudo-attribute outside PI: "//name)

    ! This is mostly ad-hoc, pseudo-attribute names are not defined anywhere.
    if (.not.checkName(name, xf%xds)) &
         call wxml_error("Invalid pseudo-attribute name: "//name)

    if (hasKey(xf%dict,name)) &
         call wxml_error(xf, "duplicate pseudo-attribute name: "//name)

    if (index(value, '?>') > 0) &
         call wxml_error(xf, "Invalid pseudo-attribute data: "//value)
    
    if (esc) then
      call add_item_to_dict(xf%dict, name, escape_string(value, xf%xds%xml_version), type=type)
    else
      call add_item_to_dict(xf%dict, name, value, type=type)
    endif
#endif
  end subroutine xml_AddPseudoAttribute_Ch


  subroutine xml_EndElement(xf, name)
    type(xmlf_t), intent(inout)             :: xf
    character(len=*), intent(in)            :: name

    character :: dummy
#ifndef DUMMYLIB
    call check_xf(xf)
    ! No point in doing checkChars, name is compared to stack anyway.

    if (len(xf%stack) == 0) &
      call wxml_fatal(xf,'Trying to close '//name//' but no tags are open.')

    if (get_top_elstack(xf%stack) /= name) &
      call wxml_fatal(xf, 'Trying to close '//name//' but '//get_top_elstack(xf%stack)// &
      ' is open. Either you have failed to open '//get_top_elstack(xf%stack)//&
      ' or you have failed to close '//name//'.') 
    xf%indent = xf%indent - indent_inc

    select case (xf%state_2)
    case (WXML_STATE_2_INSIDE_ELEMENT)
      call checkNamespacesWriting(xf%dict, xf%nsDict, len(xf%stack))
      if (getLength(xf%dict) > 0) call write_attributes(xf)
      if (xf%preserve_whitespace) call add_eol(xf)
      call add_to_buffer("/>",xf%buffer, .false.)
      dummy = pop_elstack(xf%stack)
    case (WXML_STATE_2_OUTSIDE_TAG, WXML_STATE_2_IN_CHARDATA, WXML_STATE_2_INSIDE_PI)
      if (xf%state_2==WXML_STATE_2_INSIDE_PI) call close_start_tag(xf)
      if (.not.xf%preserve_whitespace.and.xf%state_2==WXML_STATE_2_OUTSIDE_TAG) call add_eol(xf)
! XLF does a weird thing here, and if pop_elstack is called as an 
! argument to the call, it gets called twice. So we have to separate
! out get_top_... from pop_...
      call add_to_buffer("</" //get_top_elstack(xf%stack), xf%buffer, .false.)
      dummy = pop_elstack(xf%stack)
      if (xf%preserve_whitespace) call add_eol(xf)
      call add_to_buffer(">", xf%buffer, .false.)
    end select

    call checkEndNamespaces(xf%nsDict, len(xf%stack)+1)
    if (is_empty(xf%stack)) then
      xf%state_1 = WXML_STATE_1_AFTER_ROOT
    endif
    xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
#endif
  end subroutine xml_EndElement


  subroutine xml_DeclareNamespace(xf, nsURI, prefix, xml)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in) :: nsURI
    character(len=*), intent(in), optional :: prefix
    logical, intent(in), optional :: xml

#ifndef DUMMYLIB
    call check_xf(xf)
    if (.not.checkChars(nsURI, xf%xds%xml_version)) call wxml_error("xml_DeclareNamespace: Invalid character in nsURI")
    if (present(prefix)) then
      if (.not.checkChars(prefix, xf%xds%xml_version)) call wxml_error("xml_DeclareNamespace: Invalid character in prefix")
    endif

    if (xf%state_1 == WXML_STATE_1_AFTER_ROOT) &
      call wxml_error(xf, "adding namespace outside element content")

    if (len(nsURI) == 0) &
      call wxml_error(xf, "adding namespace with empty URI")
    
    if (present(prefix)) then
      call addPrefixedNS(xf%nsDict, prefix, nsURI, len(xf%stack)+1, xf%xds, xml)
    else
      call addDefaultNS(xf%nsDict, nsURI, len(xf%stack)+1)
    endif
#endif
  end subroutine xml_DeclareNamespace


  subroutine xml_UndeclareNamespace(xf, prefix)
    type(xmlf_t), intent(inout)   :: xf
    character(len=*), intent(in), optional :: prefix

#ifndef DUMMYLIB
    call check_xf(xf)
    !No need to checkChars, prfix is checked against stack

    if (xf%xds%xml_version == XML1_0) &
      call wxml_error("cannot undeclare namespaces in XML 1.0")
    
    if (xf%state_1 == WXML_STATE_1_AFTER_ROOT) &
      call wxml_error(xf, "Undeclaring namespace outside element content")
    
    if (present(prefix)) then
      call addPrefixedNS(xf%nsDict, prefix, "", len(xf%stack)+1, xf%xds)
    else
      call addDefaultNS(xf%nsDict, "", len(xf%stack)+1)
    endif
#endif
  end subroutine xml_UndeclareNamespace


  subroutine xml_Close(xf, empty)
    type(xmlf_t), intent(inout)   :: xf
    logical, optional :: empty

#ifndef DUMMYLIB
    logical :: empty_

    if (present(empty)) then
      empty_ = empty
    else
      empty_ = .false.
    endif

    if (xf%lun == -1) &
      call wxml_fatal('Tried to close XML file which is not open')

    if (xf%state_2 == WXML_STATE_2_INSIDE_PI) &
      call close_start_tag(xf)

    if (xf%state_3 /= WXML_STATE_3_BEFORE_DTD &
      .and. xf%state_3 /= WXML_STATE_3_AFTER_DTD) then
      select case (xf%state_3)
      case (WXML_STATE_3_DURING_DTD)
        call add_to_buffer('>', xf%buffer, .false.)
      case (WXML_STATE_3_INSIDE_INTSUBSET)
        call add_eol(xf)
        call add_to_buffer(']>', xf%buffer, .false.)
      end select
      xf%state_3 = WXML_STATE_3_AFTER_DTD
    endif
    
    do while (xf%state_1 == WXML_STATE_1_DURING_ROOT)
      call xml_EndElement(xf, get_top_elstack(xf%stack))
    enddo

    if (xf%state_1 /= WXML_STATE_1_AFTER_ROOT) then
      if (empty_) then
        call wxml_warning(xf, 'Invalid XML document produced: No root element')
      else
        call wxml_error(xf, 'Invalid XML document produced: No root element')
      endif
    endif
    
    call dump_buffer(xf%buffer)
    close(unit=xf%lun)
    xf%lun = -1

    call destroy_dict(xf%dict)
    call destroy_elstack(xf%stack)
    
    call destroyNamespaceDictionary(xf%nsDict)
    call destroy_xml_doc_state(xf%xds)
    
    deallocate(xf%name)
#endif
  end subroutine xml_Close


  pure function xmlf_name(xf) result(fn)
    type (xmlf_t), intent(in) :: xf
#ifdef DUMMYLIB
    character(len=1) :: fn
    fn = " "
#else
    character(len=size(xf%xds%documentURI)) :: fn
    fn = str_vs(xf%xds%documentURI)
#endif
  end function xmlf_name

#ifndef DUMMYLIB
  pure function xmlf_opentag_len(xf) result(n)
    type (xmlf_t), intent(in) :: xf
    integer :: n
    
    if (xf%lun == -1) then
      n = 0
    elseif (is_empty(xf%stack)) then
      n = 0
    else
      n = len(get_top_elstack(xf%stack))
    endif
  end function xmlf_opentag_len
#endif

  function xmlf_opentag(xf) result(fn)
    type (xmlf_t), intent(in) :: xf
#ifdef DUMMYLIB
    character(len=1) :: fn
    fn = " "
#else
    character(len=xmlf_opentag_len(xf)) :: fn
    
    if (xf%lun == -1) then
      fn = ''
    elseif (is_empty(xf%stack)) then
      fn = ''
    else
      fn = get_top_elstack(xf%stack)
    endif
#endif
  end function xmlf_opentag

#ifndef DUMMYLIB

  subroutine check_xf(xf)
    type(xmlf_t), intent(inout)   :: xf
    if (xf%lun == -1) &
      call wxml_fatal("Tried to manipulate an XML File which is not open")

  end subroutine check_xf


  subroutine add_eol(xf)
    type(xmlf_t), intent(inout)   :: xf
    
    integer :: indent_level
    
    ! In case we still have a zero-length stack, we must make
    ! sure indent_level is not less than zero.
    if (xf%state_3 == WXML_STATE_3_INSIDE_INTSUBSET) then
      indent_level = indent_inc
    else
      indent_level = xf%indent
    endif
    
    !We must flush here (rather than just adding an eol character)
    !since we don't know what the eol character is on this system.
    !Flushing with a linefeed will get it automatically, though.
    call dump_buffer(xf%buffer, lf=.true.)
    call reset_buffer(xf%buffer, xf%lun, xf%xds%xml_version)
    
    if (.not.xf%preserve_whitespace) &
      call add_to_buffer(repeat(' ',indent_level),xf%buffer, .false.)

  end subroutine add_eol
  

  subroutine close_start_tag(xf)
    type(xmlf_t), intent(inout)   :: xf

    select case (xf%state_2)
    case (WXML_STATE_2_INSIDE_ELEMENT)
      call checkNamespacesWriting(xf%dict, xf%nsDict, len(xf%stack))
      if (getLength(xf%dict) > 0)  call write_attributes(xf)
      if (.not.xf%preserve_whitespace) then
        call add_to_buffer('>', xf%buffer, .false.)
      else
        call add_eol(xf)
        call add_to_buffer('>', xf%buffer, .false.)
      endif
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    case (WXML_STATE_2_INSIDE_PI)
      if (getLength(xf%dict) > 0)  call write_attributes(xf)
      call add_to_buffer('?>', xf%buffer, .false.)
      xf%state_2 = WXML_STATE_2_OUTSIDE_TAG
    case (WXML_STATE_2_IN_CHARDATA)
      continue
    case (WXML_STATE_2_OUTSIDE_TAG)
      continue
    end select

  end subroutine close_start_tag


  subroutine write_attributes(xf)
    type(xmlf_t), intent(inout)   :: xf

    integer  :: i, j, size
    
    if (xf%state_2 /= WXML_STATE_2_INSIDE_PI .and. &
      xf%state_2 /= WXML_STATE_2_INSIDE_ELEMENT) &
      call wxml_fatal("Internal library error")
    
    do i = 1, getLength(xf%dict)
      size = len(get_key(xf%dict, i)) + len(get_value(xf%dict, i)) + 4
      if ((len(xf%buffer) + size) > COLUMNS) then
        call add_eol(xf)
      else
        call add_to_buffer(" ", xf%buffer, .false.)
      endif
      call add_to_buffer(get_key(xf%dict, i), xf%buffer, .false.)
      call add_to_buffer("=", xf%buffer, .false.)
      call add_to_buffer('"',xf%buffer, .false.)
      j = getWhiteSpaceHandling(xf%dict, i)
      if (j==0) then
        call add_to_buffer(get_value(xf%dict, i), xf%buffer, .true.)
      elseif (j==1) then
        call add_to_buffer(get_value(xf%dict, i), xf%buffer)
      else
        call add_to_buffer(get_value(xf%dict, i), xf%buffer, .false.)
      endif
      call add_to_buffer('"', xf%buffer, .false.)
    enddo

  end subroutine write_attributes
  
  subroutine wxml_warning_xf(xf, msg)
    ! Emit warning, but carry on.
    type(xmlf_t), intent(in) :: xf
    character(len=*), intent(in) :: msg
    
    if (xf%xds%warning) then
      write(6,'(a)') 'WARNING(wxml) in writing to file ', xmlf_name(xf)
      write(6,'(a)')  msg
    endif
    
  end subroutine wxml_warning_xf
  
  
  subroutine wxml_error_xf(xf, msg)
    ! Emit error message, clean up file and stop.
    type(xmlf_t), intent(inout) :: xf
    character(len=*), intent(in) :: msg
    
    write(6,'(a)') 'ERROR(wxml) in writing to file ', xmlf_name(xf)
    write(6,'(a)')  msg
    
    !call xml_Close(xf)
    stop
    
  end subroutine wxml_error_xf
  
  
  subroutine wxml_fatal_xf(xf, msg)
    !Emit error message and abort with coredump. Does not try to
    !close file, so should be used from anything xml_Close might
    !itself call (to avoid infinite recursion!)
    
    type(xmlf_t), intent(in) :: xf
    character(len=*), intent(in) :: msg
    
    write(6,'(a)') 'ERROR(wxml) in writing to file ', xmlf_name(xf)
    write(6,'(a)')  msg
    
    call pxfabort()
    stop
    
  end subroutine wxml_fatal_xf

#endif

end module m_wxml_core
