# $Id: s.xoRDF.xotcl 1.23 01/03/23 21:55:32+01:00 neumann@somewhere.wu-wien.ac.at $
package provide xotcl::rdf::parser 0.9

package require xotcl::xml::parser
#package require xotcl::pattern::link
package require xotcl::trace

##############################################################################
#
#  RDF Parse Type Handling for RDF Node Class and RDF Parser class
#  to be used as mixin. Here, we have decomposed the parse type handling
#
##############################################################################

#
#  Nodes just call "isParseLiteral", "isParseResource", and "handleParseType"
#  by their template methods -> mixins concretizes implementation
#
Class RDFNodeParseTypeHandling

#
# parseType=literal nodes are not parsed, but handled as literals
# -> the XML parser should parse these nodes -> we have cut them off
# if we encounter "parseType = literal" nextParsedLiterals searches the
# parseLiterals array and returns the content
#
RDFNodeParseTypeHandling instproc nextParsedLiterals {} {
  set parser [[self] set parser]
  $parser set parseLiterals([incr ${parser}::parseLiteralsCount])
}

#
# handle attributes that determine the parse type
#
RDFNodeParseTypeHandling instproc handleParseType value {
  if {$value == "Resource"} {
    [self] set parseResource 1
  } else {
    # with RDF 1.0 all values other than Resource are treated
    # as parseType = literal
    [self] set pcdata [list "" [[self] nextParsedLiterals]]
    [self] set parseLiteral 1
  }
}

#
# two convinience methods that tell us whether the parse type is literal/resource
#
RDFNodeParseTypeHandling instproc isParseLiteral {} {
  #
  # if the parse literal var is set -> one child
  # is of type ParseTypeLiteral !
  #
  [self] exists parseLiteral
}
RDFNodeParseTypeHandling instproc isParseResource {} {
  #
  # if the parseResource var is set -> one child
  # is of type ParseTypeResource !
  #
  [self] exists parseResource
}

#
# and we overload the Parser's parse method in order to cut off
# all parseType = "Literal", because we have to hinder the XML
# parser to parse RDF text that is marked as parseType = literal
# we store the result in an array "parseLiterals" that is used
# by the RDFNodeParseTypeHandling Mixin
#
Class RDFParserParseTypeHandling
RDFParserParseTypeHandling instproc parse data {
  [self] array set parseLiterals {}
  [self] set parseLiteralsCount 0
  set count 0

  set dt $data

  while {[set pt [string first "parseType" $dt]] != -1} {
    # we cut the string off manually, because a regexp is slower
    if {$::tcl_version > 8.0} {
      set last [string first "=" $dt $pt]
    } else {
      set last [string first "=" [string range $dt $pt end]]
      incr last $pt
    }
    set ptStart [expr {[string last "<" [string range $dt 0 $pt]] + 1}]
    set propName [string range $dt $ptStart $pt]
    set blank [string first " " $propName]
    if {$blank != -1} {
      set propName [string range $propName 0 [expr {$blank -1}]]
    }
    set dt [string range $dt $last end]
    # All parse types != Resource treated as literals
    if {![regexp {^= *[\"']Resource} $dt]} {
      regexp -indices ">" $dt idx
      set start [lindex $idx 1]
      if {[regexp -indices "</$propName>" $dt idx]} {
	set endTagLeft [lindex $idx 0]
	set literal [string range $dt [expr {$start + 1}] [expr {$endTagLeft - 1}]]
	set dt [string range $dt $endTagLeft end]
	set [self]::parseLiterals([incr count]) $literal
      } else {
	error "end tag for $propName missing"
      }
    }
  }
  next $data
}

##############################################################################
#
#  RDFNode Node Class
#
##############################################################################

Class RDFNode -superclass XMLNode -parameter {
  subject
  {rdfNSPrefix ""}
}
@ Class RDFNode -superclass XMLNode {
  description {
    general superclass for RDF nodes
    common properties
  }
}

#
# add mixins for parse type handling
#
RDFNode instproc init args {
  next
  [self] mixinappend RDFNodeParseTypeHandling
  set p [[self] info parent]
  if {[$p exists rdfNSPrefix]} {
    [self] set rdfNSPrefix [$p set rdfNSPrefix]
    #puts stderr "RDF Prefix defined in [self]->init to [$p set rdfNSPrefix]" 
  }
}

RDFNode instproc parseData {text} {
  if {[[self] isParseLiteral]} {return}
  next
}

#
# try to find the "subject" of the RDF statement ->
# if it not found on the actual node search the parents
#
# per default subject is ""; subclasses add subjects,
# when they encounter ID, about, ... attrs
#
RDFNode instproc getSubject {} {
  for {set o [self]} {![$o istype RDFTag]} {set o [$o info parent]} {
    if {[$o exists subject]} {return [$o set subject]}
  }
  return ""
}


#
# lets the parser construct an unique ID in the parser
#
RDFNode instproc makeID {} {
  [[self] set parser] makeID
}

#
# abstract methods that have to be concretized with parse type handling
# by a parse type mixin (or in subclass)
#
RDFNode abstract instproc isParseLiteral {}
RDFNode abstract instproc isParseResource {}
RDFNode abstract instproc handleParseType value

RDFNode instproc appendRDFType t {
  set t [[[self] resolveNS] getFullName $t]
  [self] set rdfTypes($t) 1
}

#
# get a typed node abbreviation -> convert it to
# a description + a nested rdf:type property
#
RDFNode instproc getTypedNode {name attrList} {
  set r [[self] getNestingNode RDFDescription \
	   [[self] qualifyWithRdfNsPrefix Description] $attrList]
  $r appendRDFType $name
  set r
}

#
# try to parse children corresponding to parse type or if none is given
# try to parse a child of type obj -> Description or Container
#
RDFNode instproc parseNestedChild {name attrList} {
  if {[[self] isParseResource]} {
    if {![[self] exists resourceDescription]} {
      [self] set resourceDescription \
	[[self] getNestingNode RDFDescription \
	   [[self] qualifyWithRdfNsPrefix Description] {}]
      # we have resolved parseType="resource" with a description
      # -> remove parse type attribute info ... it is not correct anymore,
      # but remember parseResource flag
      if {[[self] exists attributes(parseType)]} {
	[self] unset attributes(parseType)
      }
      if {[[self] exists attributes([set parseType [[self] qualifyWithRdfNsPrefix parseType]])]} {
	[self] unset attributes($parseType)
      }
    }
    
    set r [[[self] set resourceDescription] getPropertyNodeChild $name $attrList]
  } elseif {[[self] isParseLiteral]} {
    set r [self]
    # literal -> do nothing
  } else {
    if {[set node [[self] isNestingNode $name]] != ""} {
      set r [[self] getNestingNode $node $name $attrList]
    } else {
      set r [[self] getTypedNode $name $attrList]
    }
  }
  return $r
}

#
# step forward in the attrList
#
RDFNode instproc nextAttrNode {node attrList index} {
  upvar $index i $attrList a
  if {$node != ""} {
    set a [lreplace $a $i [expr {$i + 1}]]
  } else {
    incr i 2
  }
}

#
# create a child node of Property type and return it
#
# don't build a node for "type" properties, but append them to
# the list
#
RDFNode instproc getPropertyNodeChild {name attrList} {
  regexp "^[[self] set rdfNSPrefix]:(.*)" $name _ name
  set parser [[self] set parser]
  if {$name == "type" && [[self] istype RDFResource]} {
    # seek for resource attribute and append type to list
    set rp [[self] prependRDFPrefix resource]
    set rdfns [$parser set rdfNamespace]
    foreach {n v} $attrList {
      if {![[self] istype RDFContainerNodeClass]} {
        if {$n == $rp || $n == "resource"} {
          foreach c {Bag Alt Seq} {
            if {$v == "$rdfns$c"} {
              [self] class ::RDF$c
              [self] set memberNr 0
              [self] set ID [[self] set bagID]
              [self] unset bagID
              [self] set content [[self] prependRDFPrefix $c]
              # reclass existing li props to member
              set li [[self] prependRDFPrefix li]
              foreach child [lsort [[self] info children]] {
                if {[$child info class] == "::RDFProperty"} {
                  if {[$child set content] == $li || 
		      [$child set content] == "li"} {
                    $child class RDFMember
                    [self] giveMemberNr $child
                    $child set content $li
                  }
                }
              }
            }
          }
        }
      }
      [self] appendRDFType $v
    }
    return [self]
  } else {
    set nf [$parser set nodeFactory]
    set r [$nf getNode RDFProperty [self]::[[self] nextChild prop] $parser]
    $r set content $name
    $r parseAttributes $name $attrList
    set r
  }
}

#
# property in abbr syntax (as attribute)
#
RDFNode instproc propertyAttribute {n v} {
  set r [[self] getPropertyNodeChild $n ""]
  $r parseData $v
  set r
}

#
# check whether an attribute name matches an attributed RDFNode
# of this class or not
# return the corresponding node class
#
RDFNode instproc isAttribute {n} {
  regexp "^[[self] set rdfNSPrefix]:(.*)" $n _ n
  if {[lsearch [[[self] info class] set attributeList] $n] != -1} {
    return $n
  } elseif {$n == "xml:lang"} {
    # we create attribute for xml_lang (for recreation purposes)
    return $n
  }
  return ""
}

#
# check if name matches an node class that may be nested in [self]
#
RDFNode instproc isNestingNode {n} {
  regexp "^[[self] set rdfNSPrefix]:(.*)" $n _ n
  set cl [[self] info class]
  if {[$cl exists nestingList($n)]} {
    return [$cl set nestingList($n)]
  }
  return ""
}

RDFNode instproc getNestingNode {node name attrList} {
  set parser [[self] set parser]
  set nf [$parser set nodeFactory]
  switch $node {
    "RDFMember" - "RDFProperty" {set objName prop} 
    default {set objName res}
  }
  set r [$nf getNode $node [self]::[[self] nextChild $objName] $parser]
  $r set content $name
  $r parseAttributes $name $attrList
  set r
}

#
# check whether the RDF namespace is redefined to another prefix
#
RDFNode instproc makeIndividualNSEntry {prefix entry} {
  if {$entry == [[[self] set parser] rdfNamespace]} {
    if {[[self] set rdfNSPrefix] == "" || $prefix != "xmlns"} {
      [self] set rdfNSPrefix $prefix
    }
    #puts stderr "RDF Prefix redefined in [self] to $prefix"
  }
  next
}

RDFNode instproc qualifyWithRdfNsPrefix t {
  set ns [[self] set rdfNSPrefix]
  if {$ns == "xmlns"} {return $t}
  return $ns:$t
}

#
# checks whether a given attribute is part of the attributes array
# and returns the varname, otherwise ""
#
RDFNode instproc getAttribute {n nsFullName} {
  set ns [[self] resolveNS]
  set xmlns [$ns searchPrefix xmlns]
  if {$xmlns == $nsFullName && [[self] exists attributes($n)]} {
    return attributes($n)
  }
  set prefix [$ns searchFullName $nsFullName]
  if {$prefix != "" &&
      [[self] exists attributes($prefix:$n)]} {
    return attributes($prefix:$n)
  }
  return ""
}

#
# searches for attribute "n" with rdf namespace prefix
#
RDFNode instproc getRDFAttribute {n} {
  if {[[self] exists attributes($n)]} {
    return [[self] set attributes($n)]
  }
  set rdfNSPrefix [[self] set rdfNSPrefix]
  if {$rdfNSPrefix != "xmlns"} {
    set n $rdfNSPrefix:$n
    if {[[self] exists attributes($n)]} {
      return [[self] set attributes($n)]
    }
  }
  return ""
}

RDFNode instproc prependRDFPrefix ts {
  set rdfNSPrefix [[self] set rdfNSPrefix]
  if {$rdfNSPrefix != "xmlns"} {set ts $rdfNSPrefix:$ts}
  return $ts
}

##############################################################################
#
# superclass for all resources (like Description, Alt, Seq, Beg)
# used directly in the parse tree ... resource nodes are mixed in
#
##############################################################################

Class RDFResource -superclass RDFNode

RDFResource instproc print {} {
  set t [array names [self]::rdfTypes]
  if {$t == ""} {return [next]} else {return "[next]\nTYPES: $t"}
}


##############################################################################
#
# superclasses for container node classes (alt seq bag)
#
##############################################################################
Class RDFContainerNodeClass -superclass RDFResource

RDFContainerNodeClass instproc init args {
  # cache the member number
  # 0 inidicates, there is currently no member
  next

  [self] set memberNr 0
  [self] set ID [[self] makeID]
  [self] appendRDFType [[self] qualifyWithRdfNsPrefix \
			  [[[self] info class] set content]]
}

RDFContainerNodeClass instproc parseAttributes {name attrList} {
  #set index 0
  foreach {n v} $attrList {
    if {[set an [[self] isAttribute $n]] != ""} {
      [self] set attributes($n) $v
      if {$an == "ID"} {	
	[self] set subject $v
	[self] set ID [[[self] set parser] set baseURL]\#$v
      }
    }
    #set attrList [[self] nextAttrNode $an attrList index]
  }
}

RDFContainerNodeClass instproc giveMemberNr {member} {
  set pf [[self] getContentPrefix]
  if {$pf != ""} {append pf ":"}
  $member set memberIndex "${pf}_[incr [self]::memberNr]"
}

RDFContainerNodeClass instproc parseStart {name attrList} {
  set r [self]
  next
  if {[set node [[self] isNestingNode $name]] != ""} {
    set r [[self] getNestingNode $node $name $attrList]
    if {[$r info class] == "::RDFMember"} {
      [self] giveMemberNr $r
    }
  } else {
    set r [[self] getPropertyNodeChild $name $attrList]
  }
  return $r
}

##############################################################################
#
# Concrete Factory for creating RDF-style nodes
#
##############################################################################
Class RDFNodeClassFactory -superclass XMLNodeClassFactory
RDFNodeClassFactory instproc content content {
  [self] set content $content
}
RDFNodeClassFactory instproc attributeList attributeList {
  [self] set attributeList $attributeList
}
RDFNodeClassFactory instproc nestingTo nestingTo {
  set name [string trimleft [self] :]
  foreach cl $nestingTo {
    $cl set nestingList([[self] set content]) $name
  }
}

RDFNodeClassFactory proc create args {
  # create the class
  set name [next]
  switch -exact $name {
    RDFDescription - RDFProperty - RDFMember {
      [self] array set attributeList {}
    }
    RDFMember - RDFProperty {
      [self] array set nestingList {}
    }
  }
}
##########################################################################
#
# now create a factory and build all the node classes
# needed for the RDF Parser/Interpreter
#
##########################################################################
RDFNodeClassFactory proc createFactories {} {
  foreach {name superclasses content attributeList} {
    RDFTag 	  RDFNode		        RDF     {}
    RDFBag 	  RDFContainerNodeClass 	Bag     {ID}
    RDFSeq 	  RDFContainerNodeClass 	Seq     {ID}
    RDFAlt 	  RDFContainerNodeClass 	Alt     {ID}
    RDFProperty   RDFNode	                ""      {bagID ID resource parseType}
    RDFMember 	  RDFProperty         	        li      {resource parseType}
    RDFDescription  RDFResource         	Description {ID bagID about type aboutEach aboutEachPrefix}
  } {
    #puts "Create class: $name -superclass $superclasses"
    RDFNodeClassFactory create $name -superclass $superclasses \
	-content $content \
	-attributeList $attributeList
  }
}
RDFNodeClassFactory createFactories

#
# define nesting constraints
#
RDFTag nestingTo {}
RDFBag nestingTo {RDFTag RDFProperty}
RDFSeq nestingTo {RDFTag RDFProperty}
RDFAlt nestingTo {RDFTag RDFProperty}
RDFMember nestingTo {RDFContainerNodeClass RDFBag RDFSeq RDFAlt}
RDFProperty nestingTo {}
RDFDescription nestingTo {RDFTag RDFMember RDFProperty}

##############################################################################
#
# add some methods to the property node class
#
##############################################################################

RDFProperty instproc parseAttributes {name attrList} {
  set r [self]
  #set index 0
  foreach {n v} $attrList {
    if {[[self] checkForXmlNS $n $v]} {continue}
    if {[set an [[self] isAttribute $n]] != ""} {
      [self] set attributes($n) $v
      if {$an == "parseType"} {[self] handleParseType $v}
    } else {
      if {![info exists abbrvPropResource]} {
	set abbrvPropResource \
	  [[self] getNestingNode RDFDescription \
	     [[self] qualifyWithRdfNsPrefix Description] {}]
      }
      $abbrvPropResource propertyAttribute $n $v
    }
    #set attrList [[self] nextAttrNode $an attrList index]
  }

  if {[info exists abbrvPropResource]} {
    # if resource attribute is given -> use it for abbr property 
    # description as about attr  
    if {[[self] exists attributes(resource)]} {
      set about [[self] set attributes(resource)]
      [self] unset attributes(resource)
    }
    if  {[[self] exists attributes([set resource [[self] qualifyWithRdfNsPrefix resource]])]} {
      set about [[self] set attributes($resource)]
      [self] unset attributes($resource)
    }
    if {[info exists about]} {
      $abbrvPropResource set attributes(about) $about
      $abbrvPropResource set subject $about
    }
  }
}
RDFProperty instproc parseStart {name attrList} {
  if {[[self] isParseLiteral]} {return [self]}
  next
  return [[self] parseNestedChild $name $attrList]
}

##############################################################################
#
# add methods to the member class
#
##############################################################################

RDFMember parameter {
  memberIndex
}

RDFMember instproc parseAttributes {name attrList} {
  #set index 0
  foreach {n v} $attrList {
    if {[set an [[self] isAttribute $n]] != ""} {
      [self] set attributes($n) $v
      if {$an == "parseType"} {[self] handleParseType $v}
    }
    #set attrList [[self] nextAttrNode $an attrList index]
  }
}

RDFMember instproc print {} {
  return "[next]\nMEMBER-INDEX: [[self] set memberIndex]"
}

##############################################################################
#
# add methods to the description node class
#
##############################################################################

RDFDescription instproc init {args} {
  next
  set ID [[self] makeID]
  [self] set subject $ID
  [self] set bagID $ID
}

RDFDescription instproc parseAttributes {name attrList} {
  set r [self]

  # if the parent is a property with an ID -> use it
  # as description subject
  set ID [[self] qualifyWithRdfNsPrefix ID]
  set parent [[self] info parent]
  if {[$parent exists attributes(ID)]} {
    [self] set subject [$parent set attributes(ID)]
  } elseif {[$parent exists attributes($ID)]} {
    [self] set subject [$parent set attributes($ID)]
  }

  foreach {n v} $attrList {
    if {[[self] checkForXmlNS $n $v]} {continue}
    if {[set an [[self] isAttribute $n]] != ""} {
      [self] set attributes($n) $v
      switch -exact $an {
	about -
	ID -
	aboutEach -
	aboutEachPrefix {
	  [self] set subject $v
	}
	bagID {
	  [self] set bagID [[[self] set parser] set baseURL]\#$v
	}
	type {
	  [self] appendRDFType $v
	}
      }
    } else {
      set r [[self] propertyAttribute $n $v]
    }
  }
  return $r
}

RDFDescription instproc parseStart {name attrList} {
  next
  return [[self] getPropertyNodeChild $name $attrList]
}

##############################################################################
#
# add some methods to the <RDF> node class
#
##############################################################################

RDFTag parameter {{startTagOn 0}}

RDFTag instproc match {c} {
  # the prefix of the topnode determines initially how the RDF 
  # namespace is named ... since several examples don't have a 
  # namespace definition for this ns, we set here a default, which
  # may be overridden by ns definitions in the XML text
  if {[regexp {^([^:]*):(.*)} $c _ pre c]} {
    [self] makeIndividualNSEntry $pre [[[self] set parser] rdfNamespace]
    #puts stderr "Making RDF namespace entry for <$pre>"
  }
  #puts "Match for $c --- Content: [set [[self] info class]::content]"
  expr {$c == [set [[self] info class]::content]}
}

RDFTag instproc parseStart {name attrList} {
  set parsed 0
  if {[set node [[self] isNestingNode $name]] != ""} {
    set r [[self] getNestingNode $node $name $attrList]
  } else {
    set r [[self] getTypedNode $name $attrList]
  }
  next
  return $r
}

RDFTag instproc parseEnd content {
  if {!([[self] startTagOn] && [[self] match $content])} {
    [[self] errorChild $content]
  }
  next
  self ;# return [self]
}

##############################################################################
#
# RDF Factory for creating node objects
#
##############################################################################
Class RDFNodeFactory -superclass XMLNodeFactory
#RDFNodeFactory instproc getNode {key objName} {
#  set r [next]
#  return $r
#}
RDFNodeFactory rdfNodeFactory -sharedNodes {RDFDescription RDFTag}


##############################################################################
#
# RDF üarser class used to access the xml parser and produce the
# rdf node tree
#
##############################################################################
Class RDFParser -superclass XMLParser -parameter {
  {baseURL "rdfdoc"}
  {rdfNamespace "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}
}

RDFParser instproc init args {
  [self] mixinappend RDFParserParseTypeHandling

  ### this special parser handles rdf:RDF tags
  [self] topLevelHandlerPattern {^([^:]*):RDF|RDF} RDFTag

  next
  [self] set nodeFactory "rdfNodeFactory"
}

RDFParser instproc makeID {} {
  [self] autoname [[self] set baseURL]\#id
}

RDFParser instproc reset {} {
  next
  set id [[self] set baseURL]\#id
  [self] autoname -reset $id
}

RDFParser instproc createTopLevelNode {name attrList} {
  set tn [next]
  #$tn makeIndividualNSEntry xmlns [[self] set rdfNamespace]
  ### toplevel node must be of type RDFTag
  if {![$tn istype RDFTag]} {
    error "Top level node must be of type RDFTag"
  }
  if {[$tn match $name]} {
    $tn set content $name
    $tn startTagOn 1

    ### use default values for rdf/default (xmlns) namespace
    #[self] makeIndividualNSEntry rdfs "http://www.w3.org/TR/1999/PR-rdf-schema-19990303#"

    foreach {n v} $attrList {
      if {[$tn checkForXmlNS $n $v]} {continue}
    }
  }
  return $tn
}

#RDFParser instproc parse data {
#  next
#}
