## -*- Ruby -*-
## DOMHASH test implementation
## 1999 by yoshidam
##
## Apr 20, 1999 Change for draft-hiroshi-dom-hash-01.txt
##

require 'xml/dom/core'
require 'digest/md5'

module XML
  module DOM
    def self.tou16(str)
      if defined?(::Encoding)
        str.encode(::Encoding::UTF_16BE).force_encoding(::Encoding::ASCII_8BIT)
      else
        str.unpack("U*").map {|v|
          if v >= 0x10000 && v <= 0x10ffff
            ## surrogate pair
            hi = ((v - 0x10000) >> 10) | 0xd800
            low = (v & 1023) | 0xdc00
            [hi, low].pack("n*")
          else
            [v].pack("n*")
          end
        }.join
      end
    end

    class Node
      def getDigest(algorithm = Digest::MD5, force = false)
        nil
      end
    end

    class Text
      def getDigest(algorithm = Digest::MD5, force = false)
        (!force && @digest) ||
          @digest = algorithm.digest([TEXT_NODE].pack("N") + DOM.tou16(nodeValue))
        @digest
      end
    end

##    class Comment
##      def getDigest(algorithm = Digest::MD5, force = false)
##        (!force && @digest) ||
##          @digest = algorithm.digest([COMMENT_NODE].pack("N") + DOM.tou16(data)).digest
##      end
##    end

    class ProcessingInstruction
      def getDigest(algorithm = Digest::MD5, force = false)
        (!force && @digest) ||
          @digest = algorithm.digest([PROCESSING_INSTRUCTION_NODE].pack("N") +
                                     DOM.tou16(target) + "\0\0" + DOM.tou16(data))
      end
    end

    class Attr
      def getDigest(algorithm = Digest::MD5, force = false)
        (!force && @digest) ||
          @digest = algorithm.digest([ATTRIBUTE_NODE].pack("N") +
                                     DOM.tou16(nodeName) + "\0\0" + DOM.tou16(nodeValue))
      end
    end

    class NamedNodeMap
      include Enumerable
    end

    class NodeList
      include Enumerable
    end

    class Element
      def getDigest(algorithm = Digest::MD5, force = false)
        return @digest if (!force && @digest)
        attr = attributes
        children = childNodes
        attr_digests = ""
        children_digests = ""
        if attr
          attr_array = attr.sort {|a, b|
            DOM.tou16(a.nodeName) <=> DOM.tou16(b.nodeName)}
          attr_array.each {|a|
            attr_digests << a.getDigest(algorithm, force)
          }
        end
        children_num = 0
        children.each {|c|
          next if c.nodeType == COMMENT_NODE
          children_num += 1
          children_digests << c.getDigest(algorithm, force)
        }
        @digest = algorithm.digest([ELEMENT_NODE].pack("N") +
                                   DOM.tou16(nodeName) +
                                   "\0\0" +
                                   [attr.length].pack("N") +
                                   attr_digests +
                                   [children_num].pack("N") +
                                   children_digests)
      end
    end

  end
end
