UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

485 lines (449 loc) 16.7 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ define.class(function(require, exports){ // internal, Reserialize tries to turn the JS datastructure the parser outputs back into valid XML exports.reserialize = function(node, spacing, indent){ if(spacing === undefined) spacing = '\t' var ret = '' var child = '' var nontextchild = false; if(node.child){ for(var i = 0, l = node.child.length; i<l; i++){ var sub = node.child[i] if(sub.tag !== '$text') nontextchild = true child += this.reserialize(sub, spacing, indent === undefined?'': indent + spacing) } } if(!node.tag) return child if(node.tag.charAt(0) !== '$'){ ret += indent + '<' + node.tag var attr = node.attr if(attr) { for(var k in attr){ var val = attr[k] if(ret[ret.length - 1] != ' ') ret += ' ' ret += k var delim = "'" if(val !== 1){ if(typeof val === 'string' && val.indexOf(delim) !== -1) delim = '"' ret += '=' + delim + val + delim } } } if(child) { if (!nontextchild) { ret += '>' + child + '</' + node.tag + '>\n' } else { ret += '>\n' + child + indent + '</' + node.tag + '>\n' } } else { ret += '/>\n' } } else{ if(node.tag == '$text') ret += node.value else if(node.tag == '$cdata') ret += indent + '<![CDATA['+node.value+']]>\n' else if(node.tag == '$comment') ret += indent + '<!--' + node.value+'-->\n' else if(node.tag == '$root') ret += child else if(node.tag == '$empty') ret += Array(node.value - 1).join('\n') } return ret } exports.childrenByTagName = function(node, name, res){ if(!res) res = [] if (!node) return res; if(node.child){ var idx = name.indexOf('/') var rest if(idx !== -1){ rest = name.slice(idx + 1) name = name.slice(0, idx) } for(var i = 0, l = node.child.length; i<l; i++){ var sub = node.child[i] if(sub.tag === name){ if(rest !== undefined){ exports.childrenByTagName(sub, rest, res) } else res.push(sub) } } } return res } exports.childByTagName = function(node, name){ if(node.child){ var idx = name.indexOf('/') var rest if(idx !== -1){ rest = name.slice(idx + 1) name = name.slice(0, idx) } for(var i = 0, l = node.child.length; i<l; i++){ var sub = node.child[i] if(sub.tag === name){ if(rest !== undefined){ return exports.childByTagName(sub, rest) } return sub } } } } exports.childByAttribute = function(node, name, value, tag){ for(var i = 0, l = node.child.length; i<l; i++){ var sub = node.child[i] if((tag === undefined || sub.tag == tag) && sub.attr && name in sub.attr){ if(value === undefined) return sub else if(sub.attr[name] == value) return sub } } } this.__trace__ = 3 exports.createChildNode = function(tag, parent){ var node = this.createNode(tag,0) this.appendChild(parent, node) return node } exports.appendChild = this.appendChild = function(node, value){ var i = 0 if(!node.child) node.child = [value] else node.child.push(value) } exports.createNode = this.createNode = function(tag, charpos){ return {tag:tag, pos: charpos} } this.atError = function(message, where){ this.errors.push({message:message, where:where}) } var isempty = /^[\r\n\s]+$/ // discard empty textnodes /* Internal Called when encountering a textnode*/ this.atText = function(value, start){ if(!value.match(isempty)){ var node = this.createNode('$text', start) node.value = this.processEntities(value, start) this.appendChild(this.node,node) } else{ var node = this.createNode('$empty', start) node.value = value.split(/\n/).length this.appendChild(this.node,node) } } /* Internal Called when encountering a comment <!-- --> node*/ this.atComment = function(value, start, end){ var node = this.createNode('$comment', start) node.value = value this.appendChild(this.node, node) } /* Internal Called when encountering a CDATA <![CDATA[ ]]> node*/ this.atCDATA = function(value, start, end){ var node = this.createNode('$cdata', start) node.value = value this.appendChild(this.node, node) } /* Internal Called when encountering a <? ?> process node*/ this.atProcess = function(value, start, end){ var node = this.createNode('$process', start) node.value = value this.appendChild(this.node, node) } /* Internal Called when encountering a tag beginning <tag */ this.atTagBegin = function(name, start, end){ var newnode = this.createNode(name, start) this.appendChild(this.node, newnode) // push the state and set it this.parents.push(this.node, this.tagname, this.tagstart) this.tagstart = start this.tagname = name this.node = newnode } /* Internal Called when encountering a tag ending > */ this.atTagEnd = function(start, end){ this.last_attr = undefined if(this.self_closing_tags && this.tagname in this.self_closing_tags || this.tagname.charCodeAt(0) == 33){ this.tagstart = this.parents.pop() this.tagname = this.parents.pop() this.node = this.parents.pop() } } /* Internal Called when encountering a closing tag </tag> */ this.atClosingTag = function(name, start, end){ this.last_attr = undefined // attempt to match closing tag if(this.tagname !== name){ this.atError('Tag mismatch </' + name + '> with <' + this.tagname+'>', start, this.tagstart) } // attempt to fix broken html //while(this.node && name !== undefined && this.tagname !== name && this.parents.length){ // this.tagname = this.parents.pop() // this.node = this.parents.pop() //} if(this.parents.length){ this.tagstart = this.parents.pop() this.tagname = this.parents.pop() this.node = this.parents.pop() } else{ this.atError('Dangling closing tag </' + name + '>', start) } } /* Internal Called when encountering a closing tag </close> */ this.atImmediateClosingTag = function(start, end){ this.atClosingTag(this.tagname, start) } /* Internal Called when encountering an attribute name name= */ this.atAttrName = function(name, start, end){ if(name == 'tag' || name == 'child'){ this.atError('Attribute name collision with JSON structure'+name, start) name = '_' + name } this.last_attr = name if(this.node.attr && name in this.node.attr){ this.atError('Duplicate attribute ' + name + ' in tag '+this.tagname, start) } if(!this.node.attr) this.node.attr = {} this.node.attr[this.last_attr] = null } /* Internal Called when encountering an attribute value "value" */ this.atAttrValue = function(val, start, end){ if(this.last_attr === undefined){ this.atError('Unexpected attribute value ' + val, start) } else{ this.node.attr[this.last_attr] = this.processEntities(val, start) } } // all magic HTML this closing tags. set this to undefined if you want XML behavior this.self_closing_tags = { 'area':1, 'base':1, 'br':1, 'col':1, 'embed':1, 'hr':1, 'img':1, 'input':1, 'keygen':1, 'link':1, 'menuitem':1, 'meta':1, 'param':1, 'source':1, 'track':1, 'wbr':1 } // todo use these var entities = { "amp":38,"gt":62,"lt":60,"quot":34,"apos":39,"AElig":198,"Aacute":193,"Acirc":194, "Agrave":192,"Aring":197,"Atilde":195,"Auml":196,"Ccedil":199,"ETH":208,"Eacute":201,"Ecirc":202, "Egrave":200,"Euml":203,"Iacute":205,"Icirc":206,"Igrave":204,"Iuml":207,"Ntilde":209,"Oacute":211, "Ocirc":212,"Ograve":210,"Oslash":216,"Otilde":213,"Ouml":214,"THORN":222,"Uacute":218,"Ucirc":219, "Ugrave":217,"Uuml":220,"Yacute":221,"aacute":225,"acirc":226,"aelig":230,"agrave":224,"aring":229, "atilde":227,"auml":228,"ccedil":231,"eacute":233,"ecirc":234,"egrave":232,"eth":240,"euml":235, "iacute":237,"icirc":238,"igrave":236,"iuml":239,"ntilde":241,"oacute":243,"ocirc":244,"ograve":242, "oslash":248,"otilde":245,"ouml":246,"szlig":223,"thorn":254,"uacute":250,"ucirc":251,"ugrave":249, "uuml":252,"yacute":253,"yuml":255,"copy":169,"reg":174,"nbsp":160,"iexcl":161,"cent":162,"pound":163, "curren":164,"yen":165,"brvbar":166,"sect":167,"uml":168,"ordf":170,"laquo":171,"not":172,"shy":173, "macr":175,"deg":176,"plusmn":177,"sup1":185,"sup2":178,"sup3":179,"acute":180,"micro":181,"para":182, "middot":183,"cedil":184,"ordm":186,"raquo":187,"frac14":188,"frac12":189,"frac34":190,"iquest":191, "times":215,"divide":247,"OElig":338,"oelig":339,"Scaron":352,"scaron":353,"Yuml":376,"fnof":402, "circ":710,"tilde":732,"Alpha":913,"Beta":914,"Gamma":915,"Delta":916,"Epsilon":917,"Zeta":918, "Eta":919,"Theta":920,"Iota":921,"Kappa":922,"Lambda":923,"Mu":924,"Nu":925,"Xi":926,"Omicron":927, "Pi":928,"Rho":929,"Sigma":931,"Tau":932,"Upsilon":933,"Phi":934,"Chi":935,"Psi":936,"Omega":937, "alpha":945,"beta":946,"gamma":947,"delta":948,"epsilon":949,"zeta":950,"eta":951,"theta":952, "iota":953,"kappa":954,"lambda":955,"mu":956,"nu":957,"xi":958,"omicron":959,"pi":960,"rho":961, "sigmaf":962,"sigma":963,"tau":964,"upsilon":965,"phi":966,"chi":967,"psi":968,"omega":969, "thetasym":977,"upsih":978,"piv":982,"ensp":8194,"emsp":8195,"thinsp":8201,"zwnj":8204,"zwj":8205, "lrm":8206,"rlm":8207,"ndash":8211,"mdash":8212,"lsquo":8216,"rsquo":8217,"sbquo":8218,"ldquo":8220, "rdquo":8221,"bdquo":8222,"dagger":8224,"Dagger":8225,"bull":8226,"hellip":8230,"permil":8240, "prime":8242,"Prime":8243,"lsaquo":8249,"rsaquo":8250,"oline":8254,"frasl":8260,"euro":8364, "image":8465,"weierp":8472,"real":8476,"trade":8482,"alefsym":8501,"larr":8592,"uarr":8593, "rarr":8594,"darr":8595,"harr":8596,"crarr":8629,"lArr":8656,"uArr":8657,"rArr":8658,"dArr":8659, "hArr":8660,"forall":8704,"part":8706,"exist":8707,"empty":8709,"nabla":8711,"isin":8712, "notin":8713,"ni":8715,"prod":8719,"sum":8721,"minus":8722,"lowast":8727,"radic":8730,"prop":8733, "infin":8734,"ang":8736,"and":8743,"or":8744,"cap":8745,"cup":8746,"int":8747,"there4":8756,"sim":8764, "cong":8773,"asymp":8776,"ne":8800,"equiv":8801,"le":8804,"ge":8805,"sub":8834,"sup":8835,"nsub":8836, "sube":8838,"supe":8839,"oplus":8853,"otimes":8855,"perp":8869,"sdot":8901,"lceil":8968,"rceil":8969, "lfloor":8970,"rfloor":8971,"lang":9001,"rang":9002,"loz":9674,"spades":9824,"clubs":9827,"hearts":9829, "diams":9830 } var entity_rx = new RegExp("&("+Object.keys(entities).join('|')+");|&#([0-9]+);|&x([0-9a-fA-F]+);","g") /* Internal Called when processing entities */ this.processEntities = function(value, start){ if(typeof value != 'string') value = new String(value) return value.replace(entity_rx, function(m, name, num, hex, off){ if(name !== undefined){ if(!(name in entities)){ this.error('Entity not found &'+m, start + off) return m } String.fromCharCode(entities[name]) } else if(num !== undefined){ return String.fromCharCode(parseInt(num)) } else if(hex !== undefined){ return String.fromCharCode(parseInt(hex, 16)) } }) } /** * @method parse * Parse an XML/HTML document, returns JS object structure that looks like this * The implemented object serialization has one limitation: dont use attributes named * 'tag' and 'child' and dont use tags starting with $sign: <$tag> * You cant use the attribute name 'tag' and 'child' * each node is a JSON-stringifyable object * the following XML * * <tag attr='hi'>mytext</tag> * * becomes this JS object: * { * tag:'$root' * child:[{ * tag:'mytag' * attr:'hi' * child:[{ * tag:'$text' * value:'mytext' * }] * }] * } * * @param {String} input XML/HTML * @return {Object} JS output structure * this.errors[] is array of [errormsg,erroroffset,errormsg,erroroffset] * You will always get the JS object as far as it managed to parse * So check parserobj.errors.length after for errorhandling * */ this.atConstructor = function(data){ if(arguments.length) return this.parse(data) } this.parse = function(source){ // lets create some state var root = this.node = this.createNode('$root',0) this.errors = [] this.parents = [] this.last_attr = undefined this.tagname = '' if(typeof source != 'string') source = source.toString() var len = source.length var pos = 0 var start = pos while(pos < len){ var ch = source.charCodeAt(pos++) if(ch == 60){ // < var next = source.charCodeAt(pos) if(next == 32 || next == 9 || next == 10 || next == 12 || next == 37 || next == 40 || next == 41 || next == 45 || next == 35 || next == 36 || next == 92 || next == 94 || (next >=48 && next <= 57)) continue // lets emit textnode since last if(start != pos - 1){ this.atText(source.slice(start, pos - 1), start, pos - 1) } if(next == 33){ // <! after = source.charCodeAt(pos+1) if(after == 45){ // <!- comment pos += 2 start = pos while(pos < len){ ch = source.charCodeAt(pos++) if(ch == 45 && source.charCodeAt(pos) == 45 && source.charCodeAt(pos + 1) == 62){ pos += 2 this.atComment(source.slice(start + 1, pos - 3), start, pos) //console.log(source.slice(start + 1, pos - 3)) break } else if(pos == len) this.atError("Unexpected end of files while reading <!--", start) } start = pos continue } if(after == 91){ // <![ probably followed by CDATA[ just parse to ]]> start = pos pos += 8 while(pos < len){ ch = source.charCodeAt(pos++) if(ch == 93 && source.charCodeAt(pos) == 93 && source.charCodeAt(pos + 1) == 62){ pos += 2 this.atCDATA(source.slice(start + 8, pos - 3), start, pos) break } else if(pos == len) this.atError("Unexpected end of file while reading <![", start) } start = pos continue } } if(next == 63){ // <? command pos++ start = pos while(pos < len){ ch = source.charCodeAt(pos++) if(ch == 63 && source.charCodeAt(pos) == 62){ pos++ this.atProcess(source.slice(start, pos - 2), start - 1, pos) break } else if(pos == len) this.atError("Unexpected end of file while reading <?", start) } start = pos continue } if(next == 47){ // </ closing tag start = pos + 1 while(pos < len){ ch = source.charCodeAt(pos++) if(ch == 62){ this.atClosingTag(source.slice(start, pos - 1), start, pos) break } else if(pos == len) this.atError("Unexpected end of file at </"+source.slice(start, pos), start) } start = pos continue } start = pos // try to parse a tag var tag = true // first name encountered is tagname while(pos < len){ ch = source.charCodeAt(pos++) // whitespace, end of tag or assign // if we hit a s if(ch == 9 || ch == 10 || ch == 12 || ch == 13 || ch ==32 || ch == 47 || ch == 61 || ch == 62){ if(start != pos - 1){ if(tag){ // lets emit the tagname this.atTagBegin(source.slice(start, pos - 1), start - 1, pos) tag = false }// emit attribute name else this.atAttrName(source.slice(start, pos - 1), start, pos) } start = pos if(ch == 62){ // > this.atTagEnd(pos) break } else if(ch == 47 && source.charCodeAt(pos) == 62){ // /> pos++ this.atImmediateClosingTag(pos) break } } else if(ch == 34 || ch == 39){ // " or ' start = pos var end = ch while(pos < len){ ch = source.charCodeAt(pos++) if(ch == end){ this.atAttrValue(source.slice(start, pos - 1), start, pos) break } else if(pos == len) this.atError("Unexpected end of file while reading attribute", start) } start = pos } else if(ch == 60) this.atError("Unexpected < while parsing a tag", start) } start = pos } } if(this.parents.length) this.atError("Missign closing tags at end", pos) return root } })