Skip to content

Instantly share code, notes, and snippets.

@hadrienl
Created February 7, 2013 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hadrienl/4732114 to your computer and use it in GitHub Desktop.
Save hadrienl/4732114 to your computer and use it in GitHub Desktop.
/**
* @module ob/plugins/wikidom/htmltowikidom,
* @requires ob/plugins/wikidom/wikidom
*/
var NS = 'ob.plugins.wikidom',
Yopw = Y.namespace(NS),
SPECIAL_CHARS = [{
LT: {
original: '<',
replacement: '⫷',
entity: '&lt;'
},
GT: {
original: '>',
replacement: '⫸',
entity: '&gt;'
},
QUOTE: {
original: '\'',
replacement: '⧘',
entity: '&#039;'
},
DOUBLE_QUOTE: {
original: '"',
replacement: '⧚',
entity: '&quot;'
}
}],
/**
* Utils to convert WikiDOM to HTML and HTML to WikiDOM
* @class Converter
* @namespace Y.ob.plugins.wikidom
* @extends Y.Base
* @abstract
* @public
*/
Converter = Y.Base.create('Converter', Y.Base, [], {
initializer: function()
{
this._init();
},
_init: function()
{
throw new Error('Can\'t instanciate abstract class');
}
}, {
ATTRS: {
/**
* HTML string value
* @attribute html
* @type String
* @public
*/
html: {
value: '',
validator: function(v)
{
return Y.Lang.isString(v);
}
},
/**
* WikiDOM object value
* @attribute wikidom
* @type Y.ob.plugins.wikidom.Document
* @public
*/
wikidom: {
valueFn: function()
{
return new Yopw.Document();
},
setter: function(v)
{
if (v.name && 'Document' === v.name)
{
return v;
}
try
{
return new Yopw.Document(v);
}
catch (e){}
}
}
}
}),
/**
* Convert HTML to WikiDOM object
* @class HTML2WikiDOM
* @namespace Y.ob.plugins.wikidom
* @extends Y.ob.plugins.wikidom.Converter
* @public
*/
HTML2WikiDOM = Y.Base.create('HTML2WikiDOM', Converter, [], {
_init: function() {},
/**
* Convert HTML value to WikiDOM object
* @method render
* @param {String} html HTML to convert
* @return Y.ob.plugins.wikidom.Document
* @public
*/
render: function(html)
{
var wikidom = new Yopw.Document();
if (html)
{
this.set('html', html);
}
html = this.get('html');
if (html.match(/^[^<>]+$/))
{
html = '<p>'+html+'</p>';
}
var node = document.createElement('div');
node.innerHTML = html;
this._parseChildren(node, wikidom);
this.set('wikidom', wikidom);
return wikidom;
},
/**
* Parse children node elements to WikiDOM Child objects.
* Recursive function.
* @method _parseChildren
* @param {Element} node Element to transform to WikiDOM object
* @param {Y.ob.plugins.wikidom.Child} parent Child where to append the new
* objects
* @private
*/
_parseChildren: function(node, parent)
{
var children = node.childNodes;
Y.each(
children,
function(c)
{
var type = c.nodeName.toLowerCase();
if ('#text' === type)
{
// Content
return this._addContent(c.textContent, parent);
}
else
if ('br' === type)
{
return this._addContent('\\n', parent);
}
else
{
c.innerHTML = c.innerHTML.replace(/(&nbsp;)|\s+/g, ' ');
// Annotation
if (type.match(new RegExp(
'^('+Yopw.Annotation.getTagsRegExp()+')$'
)))
{
if (!c.textContent)
{
return;
}
return this._parseChildren(
this._addAnnotation(c, parent),
parent
);
}
else
// Child
if (type.match(new RegExp(
'^('+Yopw.Child.getTagsRegExp()+')$'
)) &&
parent.canHaveChild(type))
{
c.innerHTML = Y.Lang.trim(c.innerHTML);
return this._parseChildren(
c,
this._addChild(c, parent)
);
}
// Trash
else
{
return this._parseChildren(c, parent);
}
}
},
this
);
},
/**
* Set content of a Child object
* @method _addContent
* @param {String} text Text to append to Content object
* @param {Y.ob.plugins.wikidom.Child} Parent to appends content text
* @private
*/
_addContent: function(text, parent)
{
var content = parent.get('content'),
text;
if (!content)
{
return;
}
content.set('text', content.get('text') + text);
},
/**
* Add a new child
* @method _addChild
* @param {Element} node Element to convert to Child
* @param {Y.ob.plugins.wikidom.Child} Parent where to append the new child
* @private
* @return Y.ob.plugins.wikidom.Child
*/
_addChild: function(node, parent)
{
var type = node.nodeName.toLowerCase(),
child = Yopw.Child.createChildFromTag(type);
return parent.addChild(child);
},
/**
* Add a new Annotation object
* @method _addAnnotation
* @param {Element} node Element to convert to annotation
* @param {Y.ob.plugins.wikidom.Child} Parent where to append the new
* annotation
* @private
* return node
*/
_addAnnotation: function(node, parent)
{
var type = node.nodeName.toLowerCase(),
annotation = Yopw.Annotation.createAnnotationFromTag(type),
content = parent.get('content'),
annotations = content.get('annotations'),
range_s = content.get('text').length,
range_e = range_s + node.textContent.length,
preva = (annotations && annotations.length) ?
annotations[annotations.length - 1] : null;
annotation.set('range_start', range_s);
// Check if this annotation is same as another one
if (preva &&
annotation.get('type') === preva.get('type') &&
preva.get('range_end') === range_s)
{
annotation = preva;
}
else
{
annotations.push(annotation);
}
annotation.set('range_end', range_e);
if (Yopw.ANNOTATION_LINK === annotation.get('type'))
{
annotation.set(
'data',
{'title': node.getAttribute('href')}
);
}
return node;
}
}),
/**
* Convert WikiDOM to HTML object
* @class WikiDOM2HTML
* @namespace Y.ob.plugins.wikidom
* @extends Y.ob.plugins.wikidom.Converter
* @public
*/
WikiDOM2HTML = Y.Base.create('WikiDOM2HTML', Converter, [],
{
_init: function() {},
/**
* Convert wikiDOM value to html
* @method render
* @param {Y.ob.plugins.wikidom.Document} wikidom WikiDOM object
* @return string
* @public
*/
render: function(wikidom)
{
var node = document.createElement('div');
if (wikidom)
{
this.set('wikidom', wikidom);
}
wikidom = this.get('wikidom');
this._parseChild(wikidom, node);
return node.innerHTML;
},
/**
* Parse a Child object. Recursive function.
* @method _parseChild
* @param {Y.ob.plugins.wikidom.Child} child Child to convert to Element
* @param {Element} parent Parent node where to append the new one
* @private
*/
_parseChild: function(child, parent)
{
var tag, node, content;
if ('document' === child.get('type'))
{
Y.each(
child.get('children'),
function(c)
{
this._parseChild(c, parent)
},
this
);
return;
}
else
{
tag = child.getTag();
node = document.createElement(tag);
parent.appendChild(node);
content = child.get('content');
if (content)
{
node.innerHTML = this._renderContent(content);
}
return this._parseChildren(child.get('children'), node);
}
},
/**
* Parse array of children
* @method _parseChildren
* @param {Array} children Array of children to send to _parseChild method
* @param {Element} parent Parent node where to append the new children
* @private
*/
_parseChildren: function(children, parent)
{
if (!Y.Lang.isArray(children)) return;
Y.each(
children,
function(c)
{
this._parseChild(c, parent);
},
this
);
},
/**
* Render a child content
* @method _renderContent
* @param {Y.ob.plugins.wikidom.Content} c Content to render
* @private
* @return String
*/
_renderContent: function(c)
{
var list = [],
annotations = c.get('annotations'),
html = this._encodeText(c.get('text')),
length = 0;
/**
* Sort annotations
*/
Y.each(
annotations,
function(a, i)
{
var pos = a.get('range_end');
if (!list[pos])
{
list[pos] = [];
}
list[pos].push({
annotation: a,
begin: false
});
pos = a.get('range_start');
if (!list[pos])
{
list[pos] = [];
}
list[pos].push({
annotation: a,
begin: true
});
}
);
/**
* Generate html
*/
if (list.length)
{
Y.each(
list,
function(annotations, i)
{
var replace = '',
pos = i + length;
Y.each(
annotations,
function(a)
{
var tag = a.annotation.getTag(a.begin);
if (a.begin)
{
replace += tag;
}
else
{
replace = tag+replace;
}
}
);
html = html.substring(0, pos)
+ replace
+ html.substring(pos);
length += replace.length;
}
);
}
return this._entitiesText(html)
.replace(/\\n/g, '<br />');
},
/**
* Encode some special caracters to make convertion easier
* @method _encodeText
* @param {String} text Text to encode
* @private
* @return String
*/
_encodeText: function(text)
{
Y.each(
SPECIAL_CHARS,
function(s)
{
text = text.replace(s.original, s.replacement);
}
);
return text;
},
/**
* Get back the encoded caracters as html entities
* @method _entitiesText
* @param {String} text Text to encode
* @private
* @return String
*/
_entitiesText: function(text)
{
Y.each(
SPECIAL_CHARS,
function(s)
{
text = text.replace(s.replacement, s.entity);
}
);
return text;
}
});
/**
* Convert HTML string to Y.ob.plugins.wikidom.Document object
* @method html2wikidom
* @param {String} html HTML to convert to WikiDOM object
* @static
* @return Y.ob.plugins.wikidom.Document
*/
Converter.html2wikidom = function(html)
{
var converter = new HTML2WikiDOM({
html: html
});
return converter.render().asJson();
};
/**
* Convert Y.ob.plugins.wikidom.Document object to HTML string
* @method wikidom2html
* @param {Y;ob.plugins.wikidom.Document} wikidom WikiDOM object to render as
* HTML
* @static
* @return String
*/
Converter.wikidom2html = function(wikidom)
{
if (!wikidom ||
!wikidom.name ||
wikidom.name !== 'Document')
{
try
{
wikidom = new Y.ob.plugins.wikidom.Document(wikidom);
}
catch (e)
{
throw new Error(
'wikidom must be a Y.ob.plugins.wikidom.Document object'
);
}
}
var converter = new WikiDOM2HTML({
wikidom: wikidom
});
return converter.render();
};
Yopw.Converter = Converter;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment