Skip to content

Commit

Permalink
Prototypal namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Oct 16, 2011
1 parent 715951f commit 64f6d56
Showing 1 changed file with 124 additions and 76 deletions.
200 changes: 124 additions & 76 deletions lib/sax.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,44 @@ var buffers = [
]

function SAXParser (strict, opt) {
clearBuffers(this)
this.q = this.c = ""
this.bufferCheckPosition = sax.MAX_BUFFER_LENGTH
this.opt = opt || {}
this.tagCase = this.opt.lowercasetags ? "toLowerCase" : "toUpperCase"
this.tags = []
this.closed = this.closedRoot = this.sawRoot = false
this.tag = this.error = null
this.strict = !!strict
this.state = S.BEGIN
this.ENTITIES = Object.create(sax.ENTITIES)
this.attribList = []

if (this.opt.xmlns) this.ns = {
"xml": ["http://www.w3.org/XML/1998/namespace"]
} // NS bindings stacks: prefix -> [uri, uri...]
var parser = this
clearBuffers(parser)
parser.q = parser.c = ""
parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH
parser.opt = opt || {}
parser.tagCase = parser.opt.lowercasetags ? "toLowerCase" : "toUpperCase"
parser.tags = []
parser.closed = parser.closedRoot = parser.sawRoot = false
parser.tag = parser.error = null
parser.strict = !!strict
parser.state = S.BEGIN
parser.ENTITIES = Object.create(sax.ENTITIES)
parser.attribList = []

// namespaces form a prototype chain.
// it always points at the current tag,
// which protos to its parent tag.
if (parser.opt.xmlns) parser.ns = Object.create(rootNS)

// mostly just for error reporting
this.position = this.line = this.column = 0
emit(this, "onready")
parser.position = parser.line = parser.column = 0
emit(parser, "onready")
}

if (!Object.create) Object.create = function (o) {
function f () { this.__proto__ = o }
f.prototype = o
return new f
}

if (!Object.getPrototypeOf) Object.getPrototypeOf = function (o) {
return o.__proto__
}

if (!Object.keys) Object.keys = function (o) {
var a = []
for (var i in o) if (o.hasOwnProperty(i)) a.push(i)
return a
}

function checkBufferLength (parser) {
Expand Down Expand Up @@ -203,6 +221,8 @@ var whitespace = "\r\n\t "
, CDATA = "[CDATA["
, DOCTYPE = "DOCTYPE"
, XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
, XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"
, rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE }

function is (charclass, c) { return charclass.indexOf(c) !== -1 }
function not (charclass, c) { return !is(charclass, c) }
Expand Down Expand Up @@ -324,47 +344,54 @@ function strictFail (parser, message) {
if (parser.strict) error(parser, message)
}

function qname (n, parser) {
var i = n.indexOf(":")
, q = i < 0 ? [ "", n ] : n.split(":")
, p = q[0]
, l = q[1]
, u = parser.ns[p] && parser.ns[p][0] || ""

if (!u && p && p != "xmlns") {
strictFail(parser, "Unbound namespace prefix: " + JSON.stringify(p))
parser.ns[p] = parser.ns[p] || []
parser.ns[p].push(p)
u = p
}

return { prefix: p, local: l, uri: u }
}

function newTag (parser) {
if (!parser.strict) parser.tagName = parser.tagName[parser.tagCase]()
parser.tag = { name : parser.tagName, attributes : {} }
if (parser.opt.xmlns) parser.tag.bindings = []
var parent = parser.tags[parser.tags.length - 1] || parser
, tag = parser.tag = { name : parser.tagName, attributes : {} }

// will be overridden if tag contails an xmlns="foo" or xmlns:foo="bar"
if (parser.opt.xmlns) tag.ns = parent.ns
parser.attribList.length = 0
}

function qname (name) {
var i = name.indexOf(":")
, qualName = i < 0 ? [ "", name ] : name.split(":")
, prefix = qualName[0]
, local = qualName[1]

// <x "xmlns"="http://foo">
if (name === "xmlns") {
prefix = "xmlns"
local = ""
}

return { prefix: prefix, local: local }
}

function attrib (parser) {
if (parser.opt.xmlns) {
var n = parser.attribName
, qn = qname(n, parser)
var qn = qname(parser.attribName)
, prefix = qn.prefix
, local = qn.local

if (n == "xmlns" || qn.prefix == "xmlns") {
if (prefix === "xmlns") {
// namespace binding attribute; push the binding into scope
// and annotate tag so binding can be popped on tag close
var prefix = n == "xmlns" ? "" : qn.local
if (prefix === "xml" && parser.attribValue !== XML_NAMESPACE) {
if (local === "xml" && parser.attribValue !== XML_NAMESPACE) {
strictFail( parser
, "xml: prefix must be bound to " + XML_NAMESPACE + "\n"
+ "Actual: " + parser.attribValue )
} else if (local === "xmlns" && parser.attribValue !== XMLNS_NAMESPACE) {
strictFail( parser
, "xmlns: prefix must be bound to " + XMLNS_NAMESPACE + "\n"
+ "Actual: " + parser.attribValue )
} else {
parser.ns[prefix] = parser.ns[prefix] || []
parser.ns[prefix].unshift(parser.attribValue)
parser.tag.bindings.push(prefix)
var tag = parser.tag
, parent = parser.tags[parser.tags.length - 1] || parser
if (tag.ns === parent.ns) {
tag.ns = Object.create(parent.ns)
}
tag.ns[local] = parser.attribValue
}
}

Expand All @@ -387,37 +414,55 @@ function attrib (parser) {
function openTag (parser, selfClosing) {
if (parser.opt.xmlns) {
// emit namespace binding events
for (var i = 0; i < parser.tag.bindings.length; i ++) {
var p = parser.tag.bindings[i]
emitNode( parser
, "onopennamespace"
, { prefix: p , uri: parser.ns[p][0] } )
var tag = parser.tag

// add namespace info to tag
var qn = qname(parser.tagName)
tag.prefix = qn.prefix
tag.local = qn.local
tag.uri = tag.ns[qn.prefix] || qn.prefix

if (tag.prefix && !tag.uri) {
strictFail(parser, "Unbound namespace prefix: "
+ JSON.stringify(parser.tagName))
}

var parent = parser.tags[parser.tags.length - 1] || parser
if (tag.ns && parent.ns !== tag.ns) {
Object.keys(tag.ns).forEach(function (p) {
emitNode( parser
, "onopennamespace"
, { prefix: p , uri: tag.ns[p] } )
})
}

// handle deferred onattribute events
for (var i = 0, l = parser.attribList.length; i < l; i ++) {
var nv = parser.attribList[i]
, n = nv[0]
, v = nv[1]
, q = qname(n, parser)
, ns = q.prefix ? q.uri : ""
, a = { name: n
, value: v
, prefix: q.prefix
, local: q.local
, uri: ns
var name = nv[0]
, value = nv[1]
, qualName = qname(name)
, prefix = qualName.prefix
, local = qualName.local
, uri = tag.ns[prefix] || ""
, a = { name: name
, value: value
, prefix: prefix
, local: local
, uri: uri
}

parser.tag.attributes[n] = a
// if there's any attributes with an undefined namespace,
// then fail on them now.
if (prefix && prefix != "xmlns" && !uri) {
strictFail(parser, "Unbound namespace prefix: "
+ JSON.stringify(prefix))
a.uri = prefix
}
parser.tag.attributes[name] = a
emitNode(parser, "onattribute", a)
}
parser.attribList.length = 0

// add namespace info to tag
var qn = qname(parser.tagName, parser)
parser.tag.prefix = qn.prefix
parser.tag.local = qn.local
parser.tag.uri = qn.uri
}

// process the tag
Expand Down Expand Up @@ -469,22 +514,25 @@ function closeTag (parser) {
parser.tagName = tagName
var s = parser.tags.length
while (s --> t) {
parser.tag = parser.tags.pop()
var tag = parser.tag = parser.tags.pop()
parser.tagName = parser.tag.name
emitNode(parser, "onclosetag", parser.tagName)
if (parser.opt.xmlns) {
// remove namespace bindings introduced by tag
while (parser.tag.bindings.length) {
var p = parser.tag.bindings.pop()
, n = parser.ns[p].shift()
emitNode(parser, "onclosenamespace", { prefix: p, uri: n })
}

var x = {}
for (var i in tag.ns) x[i] = tag.ns[i]

var parent = parser.tags[parser.tags.length - 1] || parser
if (parser.opt.xmlns && tag.ns !== parent.ns) {
// remove namespace bindings introduced by tag
Object.keys(tag.ns).forEach(function (p) {
var n = tag.ns[p]
emitNode(parser, "onclosenamespace", { prefix: p, uri: n })
})
}
}
if (t === 0) parser.closedRoot = true
parser.tagName = parser.attribValue = parser.attribName = ""
parser.attribList.length = 0
parser.tag = null
parser.state = S.TEXT
}

Expand Down

0 comments on commit 64f6d56

Please sign in to comment.