Browse Source

[wodml] drafting an odml writer

adding xonomy
adding it to the editor
adding it to the header

(cherry picked from commit 2e8fcfd)
cgars 7 years ago
parent
commit
d88cfe3772

+ 21 - 0
public/plugins/xonomy/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Michal Boleslav Měchura
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 5 - 0
public/plugins/xonomy/README.md

@@ -0,0 +1,5 @@
+# Xonomy
+
+<img src="http://www.lexiconista.com/Xonomy/xonomy3.gif" style="display: block; width: 100%; max-width: 948px; border: 2px solid #333333"/>
+
+Xonomy is a web-based, schema-driven XML editor. For demos and documentation see http://www.lexiconista.com/xonomy/

BIN
public/plugins/xonomy/add.png


BIN
public/plugins/xonomy/bin_closed.png


BIN
public/plugins/xonomy/bullet_arrow_down.png


BIN
public/plugins/xonomy/bullet_arrow_up.png


BIN
public/plugins/xonomy/callout.gif


+ 0 - 0
public/plugins/xonomy/docspec.json


BIN
public/plugins/xonomy/draghandle.gif


BIN
public/plugins/xonomy/exclamation.png


BIN
public/plugins/xonomy/loader.gif


BIN
public/plugins/xonomy/magnifier.png


BIN
public/plugins/xonomy/minus.gif


BIN
public/plugins/xonomy/plus.gif


BIN
public/plugins/xonomy/sitemap.png


BIN
public/plugins/xonomy/tag.png


+ 466 - 0
public/plugins/xonomy/xonomy.css

@@ -0,0 +1,466 @@
+#xonomyBubble .menuItem:focus {background-color: #f5f5f5 !important; outline: none;}
+#xonomyBubble .menuLabel:focus {background-color: #f5f5f5 !important; outline: none;}
+.xonomy:focus {outline: none; }
+.xonomy .tag.focused > .name { outline: 1px dotted #666666; }
+.xonomy .textnode.focused { margin-left: -2px !important; }
+.xonomy .textnode.focused > .value { border: 1px dotted #666666; }
+.xonomy .attribute.focused .attributeName { outline: 1px dotted #666666; }
+.xonomy .attributeValue.focused { outline: 1px dotted #666666; }
+.xonomy .childrenCollapsed.focused { outline: 1px dotted #666666; background-color: #ffff99 !important; }
+.xonomy .rollouter.focused { border: 1px dotted #666666 !important; background-color: #ffff99 !important; margin-left: 2px !important; }
+
+.xonomy.nerd .element > .children > .textnode .char.focused {  }
+.xonomy.nerd .element > .children > .textnode .char.focused > .selector { position: absolute; left: 0px; right: 0px; z-index: 2; background-color: #00cc00; height: 3px; bottom: -3px; }
+.xonomy.nerd .element > .children > .textnode .char.focused > .selector > .inside { position: absolute; left: 0px; right: 0px; top: -2px; bottom: -1px; background-color: transparent; }
+
+.xonomy.laic .element > .children > .textnode .char.focused { }
+.xonomy.laic .element > .children > .textnode .char.focused > .selector { position: absolute; left: 0px; right: 0px; z-index: 2; background-color: #00cc00; height: 8px; bottom: -10px; }
+.xonomy.laic .element > .children > .textnode .char.focused > .selector > .inside { position: absolute; left: 0px; right: 0px; top: -2px; bottom: -1px; background-color: transparent; }
+
+
+/*NERD MODE*/
+.xonomy.nerd { font-family: monospace; font-size: 1rem; cursor: default; background-color: #ffffff;
+	-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
+	line-height: 2em; padding-left: 5px; }
+.xonomy.nerd * { position: relative; }
+
+/*Inline formatting of names, values, punctuation*/
+.xonomy.nerd .element .punc { color: #669acc; z-index: 1;  }
+.xonomy.nerd .element > .tag { border-radius: 2px; }
+.xonomy.nerd .element > .tag.opening {  }
+.xonomy.nerd .element > .tag.opening > .punc.slash { display: none; }
+.xonomy.nerd .element > .tag.closing {  }
+.xonomy.nerd .element > .tag > .name { color: #cc3333; cursor: pointer; }
+.xonomy.nerd .element > .tag.opening > .attributes {  }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute {  }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .name { color: #ff4455; cursor: pointer; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .valueContainer { cursor: pointer; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .valueContainer > .value { color: #666666; }
+.xonomy.nerd .element > .children > .textnode > .value { color: #000000; cursor: pointer; }
+.xonomy.nerd .element > .children > .textnode > .value > .insertionPoint { display: none; }
+.xonomy.nerd .element > .children > .textnode > .value > .dots { display: none; }
+
+/*Clickable items: hover state*/
+.xonomy.nerd .element > .tag > .name:hover { color: #00cc00; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .name:hover { color: #00cc00; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .valueContainer:hover > .value { color: #00cc00; }
+.xonomy.nerd .element > .children > .textnode > .value:hover { color: #666666; }
+.xonomy.nerd .element > .children > .textnode.whitespace > .value:hover { background-color: #f2f2f2; }
+
+/*Clickable items: current state*/
+.xonomy.nerd .element.current > .tag > .name { background-color: #ffff99; color: #00cc00; padding: 0px 3px; margin: 0px -3px; border-radius: 2px; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute.current > .name { background-color: #ffff99; color: #00cc00; padding: 0px 3px; margin: 0px -3px; border-radius: 2px; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .valueContainer.current { background-color: #ffff99; padding: 0px 3px; margin: 0px -3px; border-radius: 2px; }
+.xonomy.nerd .element > .tag.opening > .attributes > .attribute > .valueContainer.current > .value { color: #00cc00; border-radius: 2px; }
+.xonomy.nerd .element > .children > .textnode.current > .value { background-color: #ffff99 !important; color: #00cc00; padding: 0px 3px; margin: 0px -3px; border-radius: 2px; }
+
+/*Read-only stuff*/
+.xonomy.nerd .readonly * { color: #aaaaaa !important; cursor: default !important;
+	-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: text !important;}
+.xonomy.nerd .element.readonly.draggable > .connector > .draghandle { cursor: move !important; }
+.xonomy.nerd .element.readonly .warner .inside { cursor: pointer !important; }
+.xonomy.nerd .readonly .plusminus { cursor: pointer !important; }
+.xonomy.nerd .readonly .childrenCollapsed { cursor: pointer !important; }
+.xonomy.nerd .readonly .textnode .char > .selector > .inside:hover { background-color: transparent !important; }
+.xonomy.nerd .invisible { display: none; }
+
+.xonomy.nerd .attribute.shy { display: none; }
+.xonomy.nerd .rollouter { background-color: #eeeeee; border-radius: 10px; margin: 0px 4px; background-image: url(bullet_arrow_down.png); background-position: center center; background-repeat: no-repeat; padding: 0px 8px; height: 16px; cursor: pointer; }
+.xonomy.nerd .rollouter.rolledout { background-image: url(bullet_arrow_up.png);  }
+.xonomy.nerd .rollouter:hover { background-color: #ffff99; }
+.xonomy.nerd .element > .tag.opening > .attributes.rolledout { display: block; padding: 0px 0px 5px 15px; border-left: 1px dotted #cccccc; margin: 0px 0px 0px 10px; }
+.xonomy.nerd .element > .tag.opening > .attributes.rolledout > .attribute { display: block; border: 0px; padding: 0px; margin: 0px; }
+.xonomy.nerd .element > .tag.opening > .attributes.rolledout > .attribute.invisible { display: none; }
+
+/*Block layout*/
+.xonomy.nerd .element { padding-left: 25px; margin-top: 5px; margin-bottom: 5px; }
+.xonomy.nerd .element .connector { border-top: 1px dotted #b6b6b6; width: 24px; height: 10px; position: absolute; top: 1em; left: 0px; }
+.xonomy.nerd .element.oneliner .connector { top: 1em; }
+.xonomy.nerd .element .children { margin-left: 10px; border-left: 1px dotted #cccccc; padding-top: 1px; padding-bottom: 1px; }
+.xonomy.nerd .element .children .textnode { padding-left: 25px; margin-top: 5px; margin-bottom: 5px; }
+
+/*Inline layout (overrides block-layout default)*/
+.xonomy.nerd .element.hasText > .children > .element { padding-left: 0px; display: inline; }
+.xonomy.nerd .element.hasText .children .connector { position: relative; top: 0px; left: 0px; display: inline; padding-right: 16px; border: 0px; }
+.xonomy.nerd .element.hasText .children .element.uncollapsible .connector { padding-right: 0px; }
+.xonomy.nerd .element.hasText .children .element.noChildren .connector { padding-right: 0px; }
+.xonomy.nerd .textnode > .connector { display: none !important; }
+.xonomy.nerd .element.hasText .children { padding-left: 25px; padding-top: 5px; padding-bottom: 5px; }
+.xonomy.nerd .element.hasText .element.hasText .children { margin-left: 0px; border-left: 0px; padding: 0px; display: inline; }
+.xonomy.nerd .element.hasText .children .textnode { padding-left: 0px; display: inline; }
+.xonomy.nerd .element.oneliner .children .textnode .dots { display: none !important; }
+
+/*One-liner layout*/
+.xonomy.nerd .element.oneliner .element { padding-left: 0px; display: inline; }
+.xonomy.nerd .element.oneliner .children { margin-left: 0px; border-left: 0px; padding: 0px; display: inline; }
+.xonomy.nerd .element.oneliner .element.hasText .children { margin-left: 0px; border-left: 0px; padding: 0px; display: inline; }
+.xonomy.nerd .element.oneliner .children .textnode { padding-left: 0px; display: inline; }
+
+/*Empty text nodes: */
+.xonomy.nerd .element .children .textnode.empty { min-height: 1em; }
+.xonomy.nerd .element .children .textnode.empty .value { cursor: pointer; }
+.xonomy.nerd .element .children .textnode.empty .value > .insertionPoint { display: inline; padding: 0px 2px; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; }
+.xonomy.nerd .element .children .textnode.empty .value > .insertionPoint > .inside { border-left: 1px solid #dddddd; }
+.xonomy.nerd .element .children .textnode.empty:first-child:last-child .value > .dots { display: inline; padding-left: 2em;}
+.xonomy.nerd .element .children .textnode.empty .value:hover { background-color: #f2f2f2; }
+.xonomy.nerd .element .children .textnode.empty .value:focus { background-color: #f2f2f2; }
+
+/*Plus-minus collapsor*/
+.xonomy.nerd .element > .connector > .plusminus { background-image: url(minus.gif); width: 9px; height: 9px; position: absolute; top: -5px; left: -5px; cursor: pointer; }
+.xonomy.nerd .element.hasText .element.oneliner .connector .plusminus { top: 4px; left: 4px; }
+
+/*Collapsed elements*/
+.xonomy.nerd .element.collapsed > .connector > .plusminus { background-image: url(plus.gif); }
+.xonomy.nerd .element.collapsed > .children { display: none !important; }
+.xonomy.nerd .element.hasText .element.oneliner.collapsed > .tag { display: none; }
+
+/*The "collapsoid" (= rectangle that hides collapsed stuff)*/
+.xonomy.nerd .element > .childrenCollapsed { display: none; border: 1px solid #cccccc; color: #999999; border-radius: 2px; margin: 0px 2px; padding: 0px 5px 0px 5px; cursor: pointer; font-size: 1rem; }
+.xonomy.nerd .element.collapsed > .childrenCollapsed { display: inline; }
+.xonomy.nerd .element > .childrenCollapsed:hover { background-color: #f4f4f4; }
+
+/*Elements with no children*/
+.xonomy.nerd .element.noChildren > .connector > .plusminus { display: none; }
+.xonomy.nerd .element.noChildren > .children { display: none; }
+.xonomy.nerd .element.noChildren > .tag.closing { display: none; }
+.xonomy.nerd .element.noChildren > .tag.opening > .punc.slash { display: inline }
+.xonomy.nerd .element.noChildren > .childrenCollapsed { display: none; }
+
+.xonomy.nerd .element.uncollapsible > .connector > .plusminus { display: none; }
+.xonomy.nerd .element.uncollapsible > .childrenCollapsed { display: none; }
+.xonomy.nerd .element.hasText .element.uncollapsible .connector { display: none; }
+
+/*Drag handle*/
+.xonomy.nerd .connector > .draghandle { display: none; width: 9px; height: 13px; background-image: url(draghandle.gif); position: absolute; top: -7px; right: 2px; cursor: move; }
+.xonomy.nerd .element.draggable > .connector > .draghandle { display: block; }
+.xonomy.nerd .element.readonly .element .draghandle { display: none; }
+.xonomy.nerd .element.hasText .element .connector .draghandle { display: none; }
+
+/*Classes involved in dragging and dropping*/
+.xonomy.nerd .dragging { color: #cccccc !important; }
+.xonomy.nerd .dragging * { color: #cccccc !important; }
+.xonomy.nerd .dragging .draghandle { opacity: 0.5; }
+.xonomy.nerd .elementDropper { display: block; margin-top: 0px; margin-bottom: 0px; height: 0px; position: relative; }
+.xonomy.nerd .elementDropper > .inside { display: block; height: 9px; width: 9px; border: 1px solid #ffffff; background-color: #dddddd; position: absolute; top: -8px; left: -6px; z-index: 10; }
+.xonomy.nerd .elementDropper:first-child > .inside { top: -1px;  }
+.xonomy.nerd .elementDropper:only-child > .inside { top: -5px;  }
+.xonomy.nerd .activeDropper > .inside { background-color: #888888; }
+
+/*Chewed selectable text*/
+.xonomy.nerd .element > .children > .textnode .char { }
+.xonomy.nerd .element > .children > .textnode .word { white-space: nowrap; }
+.xonomy.nerd .hasInlineMenu > .children > .textnode .char > .selector { position: absolute; left: 0px; right: 0px; bottom: -2px; height: 1px; background-color: #eeeeee; z-index: 2; }
+.xonomy.nerd .hasInlineMenu > .children > .textnode .char > .selector > .inside { position: absolute; left: 0px; right: 0px; bottom: -2px; top: -3px; background-color: transparent; }
+.xonomy.nerd .hasInlineMenu > .children > .textnode .char > .selector > .inside:hover { background-color: #cccccc; left: -1px; right: -1px; }
+.xonomy.nerd .element > .children > .textnode .char.on { background-color: #ffff99; }
+.xonomy.nerd .element > .children > .textnode .char.on > .selector { position: absolute; left: 0px; right: 0px; z-index: 2; background-color: #00cc00; xbackground-color: #669acc; height: 3px; bottom: -3px; }
+.xonomy.nerd .element > .children > .textnode .char.on > .selector > .inside { position: absolute; left: 0px; right: 0px; top: -2px; bottom: -1px; background-color: transparent; }
+.xonomy.nerd span.space { font-weight: bold; color: #00cc00; }
+
+/*The exclamation mark that lets you know if there is a warning attached to an element or attribute*/
+.xonomy.nerd .warner { display: none; width: 16px; margin: 0px 1px 0px 1px; }
+.xonomy.nerd .warner .inside { position: absolute; bottom: -4px; left: 0px; background-image: url(exclamation.png); background-position: 0px 0px; width: 16px; height: 16px; cursor: pointer; }
+.xonomy.nerd .element.invalid > .tag > .warner { display: inline-block; }
+.xonomy.nerd .attribute.invalid > .warner { display: inline-block; }
+
+/*Caption beside attribute values and elements*/
+.xonomy.nerd .inlinecaption:empty {display: none;}
+.xonomy.nerd .inlinecaption { font-family: Verdana, sans-serif; font-size: 0.8em; color: #999999; font-weight: normal;
+	-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: text !important;
+}
+.xonomy.nerd .element > .inlinecaption {margin-left: 0.5em; margin-right: 0.5em;}
+
+/*Pop-up box*/
+#xonomyBubble.nerd { display: none; position: absolute; z-index: 10; min-width: 100px; }
+#xonomyBubble.nerd > div.inside { box-shadow: 0px 0px 5px #99cbff; border: 1px solid #333333; background-color: #dddddd; border-radius: 2px; margin-top: 8px; }
+#xonomyBubble.nerd > div.inside > #xonomyBubbleContent { font-family: Verdana, sans-serif; font-size: 0.8rem; color: #666666; margin: 5px; padding: 5px; background-color: #ffffff; border-radius: 2px; }
+#xonomyBubble.nerd span.punc { color: #669acc; }
+
+/*When the pop-up box functions as menu or picker*/
+#xonomyBubble.nerd #xonomyBubbleContent div.menu { margin: -5px; max-height: 250px; overflow-y: auto; white-space: nowrap; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem { padding: 8px 20px 7px 10px; border-top: 1px solid #dddddd; cursor: pointer; margin-top: -1px; background-color: #ffffff;}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem div.menuLabel {margin: -8px -20px -7px -10px; padding: 8px 20px 7px 30px; background-color: #ffffff; background-image: url(plus.gif); background-position: 10px center; background-repeat: no-repeat; font-weight: bold;}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem div.menuLabel:hover {background-color: #ffffcc;}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem span.icon {display: inline-block; margin: -8px 0px -7px 0px; width: 18px;}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem span.icon img {max-height: 15px; max-width: 20px; display: inline-block;}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem span.keyCaption {float: right; margin-left: 2em; margin-right: -0.5em; color: #999999;}
+#xonomyBubble.nerd #xonomyBubbleContent div.submenu { margin: 0px -20px -7px -10px; display: none; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.expanded > div.submenu { display: block; }
+#xonomyBubble.nerd #xonomyBubbleContent div.submenu div.menuItem { padding-left: 30px; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.expanded div.menuLabel {background-image: url(minus.gif);}
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.expanded > div.menuLabel {margin-bottom: 0px; padding-bottom: 7px;}
+#xonomyBubble.nerd #xonomyBubbleContent span.techno { font-family: monospace; font-size: 0.85rem; }
+#xonomyBubble.nerd #xonomyBubbleContent span.techno span.punc {color: #669acc; }
+#xonomyBubble.nerd #xonomyBubbleContent span.techno span.atName { color: #ff4455; }
+#xonomyBubble.nerd #xonomyBubbleContent span.techno span.atValue { color: #666666; }
+#xonomyBubble.nerd #xonomyBubbleContent span.techno span.elName { color: #cc3333; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.techno { padding-top: 6px; padding-bottom: 6px; font-family: monospace; font-size: 0.9rem; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.techno span.explainer { font-family: Verdana, sans-serif; font-size: 0.8em; color: #999999; font-weight: normal; margin-left: 3px; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem.current { x-background-color: #ffffdd; }
+#xonomyBubble.nerd #xonomyBubbleContent div.menuItem:hover { background-color: #ffffcc; }
+
+/*When the pop-up box takes input from the user*/
+#xonomyBubble.nerd #xonomyBubbleContent form { margin: -5px; padding: 5px; background-color: #eeeeee; }
+#xonomyBubble.nerd #xonomyBubbleContent form.overmenu { margin-bottom: 5px; }
+#xonomyBubble.nerd #xonomyBubbleContent form.undermenu { margin-top: 5px; }
+#xonomyBubble.nerd #xonomyBubbleContent div.submitline { text-align: right; margin-top: 5px; }
+#xonomyBubble.nerd #xonomyBubbleContent input { border-width: 1px; padding: 3px; color: #333333; font: inherit; }
+#xonomyBubble.nerd #xonomyBubbleContent textarea { border-width: 1px; padding: 3px; color: #333333; font: inherit; }
+#xonomyBubble.nerd #xonomyBubbleContent input.textbox { font-family: monospace; font-size: 1rem; width: 250px; border: 1px solid #dddddd; }
+#xonomyBubble.nerd #xonomyBubbleContent textarea.textbox { font-family: monospace; font-size: 1rem; width: 400px; height: 100px; border: 1px solid #dddddd; }
+
+/*When the pop-up is a list of warnings*/
+#xonomyBubble.nerd #xonomyBubbleContent .warning { padding: 5px 10px; }
+
+#xonomyBubble.nerd button.buttonSearch { background-image: url(magnifier.png); background-position: center center; background-repeat: no-repeat; padding: 2px 15px; border-width: 1px; border-radius: 2px; }
+#xonomyBubble.nerd button.buttonCreate { background-image: url(add.png); background-position: center center; background-repeat: no-repeat; padding: 2px 15px; border-width: 1px; border-radius: 2px; }
+
+
+/*LAIC MODE*/
+.xonomy.laic { font-family: Verdana, sans-serif; font-size: 0.85rem; cursor: default; background-color: #ffffff;
+	-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
+	line-height: 1.5em; padding-left: 5px; }
+.xonomy.laic * { position: relative; }
+.xonomy.laic .element .punc { display: none; }
+.xonomy.laic .element { display: block; margin: 5px 0px; padding: 5px 0px 0px 0px; }
+.xonomy.laic .element > .children { padding-left: 40px; }
+.xonomy.laic .element > .children > .textnode { display: block; margin: 10px 0px 0px 0px; }
+.xonomy.laic .element.oneliner > .children > .textnode { margin-top: 0px; }
+.xonomy.laic .element > .children > .textnode > .value { display: inline-block; font-family: monospace; background-color: #ffffff; padding: 4px 0px; border-bottom: 1px solid #999999; cursor: pointer; min-width: 2em; }
+.xonomy.laic .element.oneliner > .children > .textnode > .value { text-align: center; }
+.xonomy.laic .element > .tag.closing { display: none; }
+.xonomy.laic .element > .tag { display: block; padding: 5px 10px; background-color: #eeeeee; box-shadow: 0px 0px 2px #999999; z-index: 9;}
+.xonomy.laic .element > .tag > .name {font-weight: bold; color: #315696; cursor: pointer;}
+.xonomy.laic .element > .tag.opening > .attributes > .attribute { margin-left: 0.75em; border-left: 1px solid #cccccc; padding-left: 0.75em; display: inline-block; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute > .name { font-weight: bold; color: #6385bf; cursor: pointer; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute > .valueContainer { display: inline; padding: 2px 5px; border-bottom: 1px solid #999999; margin-left: 10px; font-family: monospace; font-size: 0.85rem; cursor: pointer; }
+.xonomy.laic .element > .children > .textnode > .value > .insertionPoint { display: none; }
+.xonomy.laic .element > .children > .textnode > .value > .dots { display: none; }
+
+/*oneliner*/
+.xonomy.laic .element.oneliner {padding: 5px 0px 0px 0px; }
+.xonomy.laic .element.oneliner > .tag.opening { display: inline-block; min-width: 25%; margin-right: 1em; padding: 2px 10px; line-height: 2em;}
+.xonomy.laic .element.oneliner > .children { display: inline-block; padding: 0px; margin: 0px; }
+.xonomy.laic .element.oneliner > .children > .textnode { display: inline-block; }
+.xonomy.laic .element.oneliner > .children > .textnode > .value { display: inline-block; }
+.xonomy.laic .element.oneliner.hasInlineMenu { }
+.xonomy.laic .element.oneliner.hasInlineMenu > .children { padding: 0px 0px; line-height: inherit;}
+.xonomy.laic .element.oneliner.hasInlineMenu > .children > .textnode {  }
+.xonomy.laic .element.oneliner.hasInlineMenu > .children > .textnode > .value {  }
+
+.xonomy.laic .element .connector { position: absolute; top: -0.5em;}
+.xonomy.laic .element.oneliner .connector { position: relative; top: 0px;}
+
+/*Plus-minus collapsor*/
+.xonomy.laic .element > .connector > .plusminus { background-image: url(minus.gif); background-position: center center; background-repeat: no-repeat; width: 1rem; height: 1rem; background-color: #dddddd; position: absolute; top: 20px; left: -30px; cursor: pointer; }
+.xonomy.laic .element.oneliner > .connector > .plusminus { top: 1px; }
+.xonomy.laic .element.hasInlineMenu .element .connector > .plusminus { position: relative; top: auto; left: auto; display: inline-block; margin-right: 3px; }
+
+/*Collapsed elements*/
+.xonomy.laic .element.collapsed > .connector > .plusminus { background-image: url(plus.gif); }
+.xonomy.laic .element.collapsed > .children { display: none !important; }
+.xonomy.laic .element.hasText .element.oneliner.collapsed > .tag { display: none; }
+
+/*The "collapsoid" (= rectangle that hides collapsed stuff)*/
+.xonomy.laic .element > .childrenCollapsed { display: none; color: #999999; padding: 1px 5px; cursor: pointer; font-family: monospace; font-size: 0.85rem; z-index: 9; text-shadow: 0px 0px 2px #ffffff;}
+.xonomy.laic .element.collapsed > .childrenCollapsed { display: block; margin-top: 5px; }
+.xonomy.laic .element.collapsed > .childrenCollapsed:hover { background-color: #f4f4f4; }
+
+/*Caption beside attribute values and elements*/
+.xonomy.laic .inlinecaption:empty {display: none;}
+.xonomy.laic .inlinecaption {-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: text !important;}
+.xonomy.laic .attribute > .inlinecaption { font-family: Verdana, sans-serif; font-size: 0.75rem; color: #999999; font-weight: normal; margin-left: 5px; margin-right: 4px; }
+.xonomy.laic .element > .inlinecaption { font-family: Verdana, sans-serif; font-size: 0.75rem; color: #999999; font-weight: normal; display: block; position: absolute; top: 10px; right: 3px; z-index: 9; }
+.xonomy.laic .element.oneliner > .inlinecaption { display: inline; margin-left: 1em; margin-right: 4px; position: relative; top: auto; right: auto;}
+
+/*Elements with no children*/
+.xonomy.laic .element.noChildren > .connector > .plusminus { display: none; }
+.xonomy.laic .element.noChildren > .children { display: none; }
+.xonomy.laic .element.noChildren > .childrenCollapsed { display: none; }
+
+/*Elements that cannot be collapsed*/
+.xonomy.laic .element.uncollapsible {padding-left: 0px !important; }
+.xonomy.laic .element.uncollapsible > .connector > .plusminus { display: none !important; }
+.xonomy.laic .element.uncollapsible > .childrenCollapsed { display: none !important; }
+
+.xonomy.laic .element.hasText .element {border: 0px; }
+.xonomy.laic .element.hasInlineMenu > .children {background-color: #ffffff; padding: 7px 10px 0px 40px; line-height: 2.5em; }
+.xonomy.laic .element.hasInlineMenu .element {display: inline; margin-top: 0px;}
+.xonomy.laic .element.hasInlineMenu .element > .tag.opening {display: inline; background-color: #eeeeee; border-radius: 20px 0px 0px 20px; margin: 0px 2px 0px 2px; padding: 2px 0px 2px 5px; }
+.xonomy.laic .element.hasInlineMenu .element > .tag.closing {display: inline; background-color: #eeeeee; border-radius: 0px 20px 20px 0px; margin: 0px 2px 0px 2px; padding: 2px 5px 2px 0px;}
+.xonomy.laic .element.hasInlineMenu .element > .tag.opening > .name {margin-right: 5px;}
+.xonomy.laic .element.hasInlineMenu .element > .tag.closing > .name {margin-left: 5px;}
+.xonomy.laic .element.hasInlineMenu .element > .tag.opening > .attributes > .attribute { margin-left: 0.25em; padding-left: 0.25em; padding-right: 0.5em !important; }
+.xonomy.laic .element.hasInlineMenu .element > .children {display: inline; }
+.xonomy.laic .element.hasInlineMenu .textnode {display: inline; border: 0px; margin: 0px; padding: 0px; }
+.xonomy.laic .element.hasInlineMenu .textnode .value {display: inline; xborder: 0px; padding: 4px 0px; }
+.xonomy.laic .element.hasInlineMenu .element .textnode .value {display: inline; xborder: 0px; padding: 6px 0px; }
+.xonomy.laic .element.hasInlineMenu .element .children {border: 0px; padding: 0px;}
+.xonomy.laic .element.hasInlineMenu .element .childrenCollapsed { display: none; position: relative; top: auto; right: auto; }
+.xonomy.laic .element.hasInlineMenu .element.collapsed .childrenCollapsed {display: inline;}
+.xonomy.laic .element.hasInlineMenu .element > .connector > .plusminus { top: 3px; }
+.xonomy.laic .element.hasInlineMenu .element .name {padding: 2px 5px; }
+.xonomy.laic .element.hasInlineMenu .element > .tag.opening .attribute .name {  }
+.xonomy.laic .element.hasInlineMenu .element > .tag.opening .attribute .valueContainer { padding: 0px 5px !important; margin-left: 0px; }
+.xonomy.laic .element.hasInlineMenu .children > .textnode { margin-top: 0px; }
+
+/*Clickable items: hover state*/
+.xonomy.laic .element > .tag > .name:hover { color: #6385bf; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute > .name:hover { color: #819bc7; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute > .valueContainer:hover  { background-color: #f6f3e6; }
+.xonomy.laic .element > .children > .textnode > .value:hover { color: #666666; background-color: #f6f3e6; }
+.xonomy.laic .element > .children > .textnode.whitespace > .value:hover { background-color: #f6f3e6; }
+
+/*Clickable items: current state*/
+.xonomy.laic .element.current > .tag > .name { background-color: #ffff99; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute.current > .name { background-color: #ffff99; }
+.xonomy.laic .element > .tag.opening > .attributes > .attribute > .valueContainer.current { background-color: #ffffcc; }
+.xonomy.laic .element > .children > .textnode.current > .value { background-color: #ffffcc; }
+
+/*Empty text nodes: */
+.xonomy.laic .element .children .textnode.empty {  }
+.xonomy.laic .element .children .textnode.empty .value { cursor: pointer; padding-right: 0.05em; padding-left: 0.05em; border-bottom-width: 0px; }
+.xonomy.laic .element .children .textnode.empty .value > .insertionPoint { display: inline; padding: 0px 2px; border-top: 1px solid #dddddd; border-bottom: 1px solid #dddddd; visibility: hidden; }
+.xonomy.laic .element .children .textnode.empty .value > .insertionPoint > .inside { border-left: 1px solid #dddddd; }
+.xonomy.laic .element .children .textnode.empty .value:hover { background-color: #f6f3e6; }
+.xonomy.laic .element .children .textnode.empty .value:focus { background-color: #ffffcc; }
+.xonomy.laic .element .children .textnode.empty:only-child .value {xpadding-right: 0.5em; border-bottom-width: 1px; }
+
+/*Drag handle*/
+.xonomy.laic .connector > .draghandle { display: none; width: 9px; height: 13px; background-image: url(draghandle.gif); position: absolute; top: 22px; right: 3px; cursor: move; }
+.xonomy.laic  .oneliner > .connector > .draghandle {  top: 3px; }
+.xonomy.laic .element.draggable > .connector > .draghandle { display: block; }
+.xonomy.laic .element.readonly .element .draghandle { display: none; }
+.xonomy.laic .element.hasText .element .connector .draghandle { display: none; }
+
+/*Classes involved in dragging and dropping*/
+.xonomy.laic .dragging { color: #cccccc !important; }
+.xonomy.laic .dragging * { color: #cccccc !important; }
+.xonomy.laic .dragging .draghandle { opacity: 0.5; }
+.xonomy.laic .elementDropper { display: block; margin-top: 0px; margin-bottom: 0px; height: 0px; position: relative; }
+.xonomy.laic .elementDropper > .inside { display: block; height: 9px; width: 9px; border: 1px solid #ffffff; background-color: #6fbb93; position: absolute; top: -5px; left: -12px; z-index: 10; }
+.xonomy.laic .elementDropper:first-child > .inside { top: -4px;  }
+.xonomy.laic .elementDropper:only-child > .inside { top: -5px;  }
+.xonomy.laic .activeDropper > .inside { background-color: #888888; }
+
+/*Chewed selectable text*/
+.xonomy.laic .element > .children > .textnode .char { }
+.xonomy.laic .element > .children > .textnode .word { white-space: nowrap; }
+.xonomy.laic .hasInlineMenu > .children > .textnode .char > .selector { position: absolute; left: 0px; right: 0px; bottom: -7px; height: 0px; background-color: #999999; z-index: 2; }
+.xonomy.laic .hasInlineMenu > .children > .textnode .char > .selector > .inside { position: absolute; left: 0px; right: 0px; bottom: -3px; top: -5px; background-color: transparent; }
+.xonomy.laic .hasInlineMenu > .children > .textnode .char > .selector > .inside:hover { background-color: #00cc00; left: -1px; right: -1px; }
+.xonomy.laic .element > .children > .textnode .char.on { background-color: #ffff99; }
+.xonomy.laic .element > .children > .textnode .char.on > .selector { position: absolute; left: 0px; right: 0px; z-index: 2; background-color: #00cc00; height: 8px; bottom: -10px; }
+.xonomy.laic .element > .children > .textnode .char.on > .selector > .inside { position: absolute; left: 0px; right: 0px; top: -2px; bottom: -1px; background-color: transparent; }
+.xonomy.laic span.space { font-weight: bold; color: #00cc00; }
+
+/*The exclamation mark that lets you know if there is a warning attached to an element or attribute*/
+.xonomy.laic .warner { display: none; width: 16px; margin: 0px 5px 0px 1px; }
+.xonomy.laic .warner .inside { position: absolute; bottom: -4px; left: 0px; background-image: url(exclamation.png); background-position: 0px 0px; width: 16px; height: 16px; cursor: pointer; }
+.xonomy.laic .element.invalid > .tag > .warner { display: inline-block; }
+.xonomy.laic .attribute.invalid > .warner { display: inline-block; }
+
+/*Read-only stuff*/
+.xonomy.laic .readonly * { color: #aaaaaa !important; cursor: default !important;
+ 	-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; cursor: text !important;}
+.xonomy.laic .element.readonly.draggable > .connector > .draghandle { cursor: move !important; }
+.xonomy.laic .element.readonly .warner .inside { cursor: pointer !important; }
+.xonomy.laic .readonly .plusminus { cursor: pointer !important; }
+.xonomy.laic .readonly .childrenCollapsed { cursor: pointer !important; }
+.xonomy.laic .readonly .textnode .char > .selector > .inside:hover { background-color: transparent !important; }
+.xonomy.laic .invisible { display: none; }
+.xonomy.laic .readonly .textnode .value { border: 0px !important; }
+.xonomy.laic .readonly .textnode .value:hover { background-color: transparent !important; }
+
+.xonomy.laic .attribute.shy { display: none; }
+.xonomy.laic .rollouter { background-color: #dddddd; border-radius: 0px; margin: 0px 4px; position: relative; top: 1px; background-image: url(bullet_arrow_down.png); background-position: center center; background-repeat: no-repeat; padding: 3px 16px; cursor: pointer; }
+.xonomy.laic .rollouter.rolledout { background-image: url(bullet_arrow_up.png); position: relative; left: -9px; }
+.xonomy.laic .rollouter:hover { background-color: #ffff99; }
+.xonomy.laic .element > .tag.opening > .attributes.rolledout { display: block; padding: 0px 0px 5px 15px; border-left: 1px dotted #cccccc; margin: 0px 0px 0px 10px; line-height: 2em;}
+.xonomy.laic .element > .tag.opening > .attributes.rolledout > .attribute { display: block; border: 0px; padding: 0px; margin: 5px 0px; }
+.xonomy.laic .element > .tag.opening > .attributes.rolledout > .attribute.invisible { display: none; }
+
+/*Pop-up box*/
+#xonomyBubble.laic { display: none; position: absolute; z-index: 20; min-width: 100px; }
+#xonomyBubble.laic > div.inside { box-shadow: 0px 0px 5px #99cbff; border: 1px solid #333333; background-color: #dddddd; border-radius: 2px; margin-top: 8px; }
+#xonomyBubble.laic > div.inside > #xonomyBubbleContent { font-family: Verdana, sans-serif; font-size: 0.75rem; color: #666666; margin: 0px; padding: 10px; background-color: #ffffff; border-radius: 2px; }
+#xonomyBubble.laic span.punc { display: none; }
+
+/*When the pop-up box functions as menu or picker*/
+#xonomyBubble.laic #xonomyBubbleContent div.menu { margin: -10px; max-height: 250px; overflow-y: auto; white-space: nowrap; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem { padding: 10px 20px 10px 10px; border-top: 1px solid #dddddd; cursor: pointer; margin-top: -1px; background-color: #ffffff;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem div.menuLabel {margin: -8px -20px -7px -10px; padding: 8px 20px 7px 30px; background-color: #ffffff; background-image: url(plus.gif); background-position: 10px center; background-repeat: no-repeat; font-weight: bold;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem div.menuLabel:hover {background-color: #f6f6f6;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem span.icon {display: inline-block; margin: -8px 0px -7px 0px; width: 18px;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem span.icon img {max-height: 15px; max-width: 20px; display: inline-block;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem span.keyCaption {float: right; margin-left: 2em; margin-right: -0.5em; color: #999999;}
+#xonomyBubble.laic #xonomyBubbleContent div.submenu { margin: 0px -20px -7px -10px; display: none; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.expanded > div.submenu { display: block; }
+#xonomyBubble.laic #xonomyBubbleContent div.submenu div.menuItem { padding-left: 30px; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.expanded div.menuLabel {background-image: url(minus.gif);}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.expanded > div.menuLabel {margin-bottom: 0px; padding-bottom: 7px;}
+#xonomyBubble.laic #xonomyBubbleContent .techno { font-family: monospace; font-size: 0.75rem; color: #444444; }
+#xonomyBubble.laic #xonomyBubbleContent .techno span.atName { font-family: Verdana, sans-serif; font-weight: bold; color: #6385bf; }
+#xonomyBubble.laic #xonomyBubbleContent .techno span.atValue { font-family: monospace; font-size: 0.75rem; color: #444444; }
+#xonomyBubble.laic #xonomyBubbleContent span.techno span.elName { font-family: Verdana, sans-serif; font-weight: bold; color: #315696; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.techno span.explainer { font-family: Verdana, sans-serif; font-size: 1em; color: #999999; font-weight: normal; margin-left: 0.5em; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.techno span.explainer.alone {margin-left: 0px;}
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem.current { x-background-color: #ffffdd; }
+#xonomyBubble.laic #xonomyBubbleContent div.menuItem:hover { background-color: #f6f6f6; }
+
+/*When the pop-up box takes input from the user*/
+#xonomyBubble.laic #xonomyBubbleContent form { margin: -5px; padding: 5px; background-color: #eeeeee; }
+#xonomyBubble.laic #xonomyBubbleContent form.overmenu { margin-bottom: 15px; }
+#xonomyBubble.laic #xonomyBubbleContent form.undermenu { margin-top: 15px; }
+#xonomyBubble.laic #xonomyBubbleContent div.submitline { text-align: right; margin-top: 5px; }
+#xonomyBubble.laic #xonomyBubbleContent input { border-width: 1px; padding: 6px; color: #333333; font: inherit; font-size: 0.85rem; }
+#xonomyBubble.laic #xonomyBubbleContent textarea { border-width: 1px; padding: 6px; color: #333333; font: inherit; font-size: 0.85rem; }
+#xonomyBubble.laic #xonomyBubbleContent input.textbox { font-family: monospace; font-size: 1rem; width: 400px; border: 1px solid #dddddd; font-size: 0.85rem; }
+#xonomyBubble.laic #xonomyBubbleContent textarea.textbox { font-family: monospace; font-size: 1rem; width: 400px; height: 100px; border: 1px solid #dddddd; font-size: 0.85rem; }
+
+/*When the pop-up is a list of warnings*/
+#xonomyBubble.laic #xonomyBubbleContent .warning { padding: 5px 10px; }
+
+#xonomyBubble.laic button.buttonSearch { background-image: url(magnifier.png); background-position: center center; background-repeat: no-repeat; padding: 4px 15px; border-width: 1px; border-radius: 2px; }
+#xonomyBubble.laic button.buttonCreate { background-image: url(add.png); background-position: center center; background-repeat: no-repeat; padding: 4px 15px; border-width: 1px; border-radius: 2px; }
+
+
+#xonomyBubble .wyc { display: inline-block; background-image: url(loader.gif); background-position: center center; background-repeat: no-repeat; width: 100%; height: 30px; margin: 5px 0px 0px 0px; }
+.xonomy .wyc { display: inline-block; background-image: url(loader.gif); background-position: center center; background-repeat: no-repeat; width: 30px; height: 10px; }
+.xonomy .inlinecaption a { color: inherit; text-decoration: none; }
+.xonomy .inlinecaption a:hover {color: #2d4ea1;}
+
+.xonomy div.modeSwitcher { position: fixed; bottom: 0px; left: 0px; width: 70px; height: 28px; background-color: #ffffff; cursor: pointer; border: 1px solid #cccccc; z-index: 12;}
+.xonomy.nerd div.modeSwitcher {}
+.xonomy.laic div.modeSwitcher {}
+.xonomy div.modeSwitcher span.nerd {display: inline-block; width: 35px; height: 28px; background-image: url(tag.png); background-position: center center; background-repeat: no-repeat; }
+.xonomy div.modeSwitcher span.laic {display: inline-block; width: 35px; height: 28px; background-image: url(sitemap.png); background-position: center center; background-repeat: no-repeat; }
+.xonomy div.modeSwitcher span {background-color: #f3f3f3;}
+.xonomy div.modeSwitcher:hover {box-shadow: 0px 0px 1px #999999; }
+.xonomy.nerd div.modeSwitcher span.nerd {background-color: #fbeb93; }
+.xonomy.laic div.modeSwitcher span.laic {background-color: #fbeb93; }
+
+.xonomy div.layby { position: fixed; bottom: 0px; top: 0px; right: 0px; padding: 10px 10px 0px 10px; background-color: #fffef4; z-index: 12; border-left: 3px solid #cccccc; z-index: 9}
+.xonomy div.layby.closed { width: 0px; padding: 5px 10px 0px 5px; cursor: pointer; }
+.xonomy div.layby.closed:hover {background-color: #f9f6dc;}
+.xonomy div.layby span.button {display: inline-block; width: 30px; height: 30px; background-position: center center; background-repeat: no-repeat; background-color: #ffffff; border: 1px solid #cccccc; cursor: pointer;}
+.xonomy div.layby span.button:hover {box-shadow: 0px 0px 1px #333333;}
+.xonomy div.layby span.closer {background-image: url(bullet_arrow_down.png);}
+.xonomy div.layby span.purger {background-image: url(bin_closed.png); float: right;}
+.xonomy div.layby.closed span.closer { display: none; }
+.xonomy div.layby.closed span.purger { display: none; }
+.xonomy div.layby.closed div.content { display: none; }
+.xonomy div.layby.open { width: 30%; }
+.xonomy div.layby.open span.closer { display: inline-block; }
+.xonomy div.layby.open div.content { display: block; }
+.xonomy div.layby.empty span.purger { display: none; }
+.xonomy div.layby.nonempty.open span.purger { display: inline-block; }
+.xonomy div.layby div.message { display: none; position: absolute; top: 40px; left: 20px; right: 20px; bottom: 0px; overflow: hidden; font-family: sans-serif; font-size: 1.25rem; line-height: 1.5em; text-align: center; padding-top: 100px; color: #cccccc;}
+.xonomy div.layby.empty.open div.message { display: block; }
+.xonomy div.layby.activeDropper {background-color: #f9f6dc;}
+.xonomy div.layby > div.content { position: absolute; top: 50px; left: 10px; right: 10px; bottom: 0px; overflow-x: hidden; overflow-y: auto; white-space: nowrap;}
+.xonomy.laic div.layby > div.content { padding-right: 1px;}
+.xonomy.nerd div.layby > div.content > .element {margin-left: 10px;}
+.xonomy.laic div.layby > div.content > .element {margin-left: 35px;}
+.xonomy.laic div.layby > div.content .children {background-color: transparent !important;}
+.xonomy.laic div.layby > div.content .textnode > .value {background-color: transparent !important;}

+ 1873 - 0
public/plugins/xonomy/xonomy.js

@@ -0,0 +1,1873 @@
+var Xonomy={
+	lang: "", //"en"|"de"|fr"| ...
+	mode: "nerd", //"nerd"|"laic"
+};
+Xonomy.setMode=function(mode) {
+	if(mode=="nerd" || mode=="laic") Xonomy.mode=mode;
+	if(mode=="nerd") $(".xonomy").removeClass("laic").addClass("nerd");
+	if(mode=="laic") $(".xonomy").removeClass("nerd").addClass("laic");
+}
+
+Xonomy.jsEscape=function(str) {
+  return String(str)
+		.replace(/\"/g, '\\\"')
+		.replace(/\'/g, '\\\'')
+};
+Xonomy.xmlEscape=function(str, jsEscape) {
+	if(jsEscape) str=Xonomy.jsEscape(str);
+  return String(str)
+		.replace(/&/g, '&amp;')
+		.replace(/"/g, '&quot;')
+		.replace(/'/g, '&apos;')
+		.replace(/</g, '&lt;')
+		.replace(/>/g, '&gt;');
+};
+Xonomy.xmlUnscape=function(value){
+    return String(value)
+        .replace(/&quot;/g, '"')
+        .replace(/&apos;/g, "'")
+        .replace(/&lt;/g, '<')
+        .replace(/&gt;/g, '>')
+        .replace(/&amp;/g, '&');
+};
+Xonomy.isNamespaceDeclaration=function(attributeName) {
+	//Tells you whether an attribute name is a namespace declaration.
+	var ret=false;
+	if(attributeName=="xmlns") ret=true;
+	if(attributeName.length>=6 && attributeName.substring(0, 6)=="xmlns:") ret=true;
+	return ret;
+};
+Xonomy.namespaces={}; //eg. "xmlns:mbm": "http://lexonista.com"
+
+Xonomy.xml2js=function(xml, jsParent) {
+	if(typeof(xml)=="string") xml=$.parseXML(xml);
+	if(xml.documentElement) xml=xml.documentElement;
+	var js=new Xonomy.surrogate(jsParent);
+	js.type="element";
+	js.name=xml.nodeName;
+	js.htmlID="";
+	js.attributes=[];
+	for(var i=0; i<xml.attributes.length; i++) {
+		var attr=xml.attributes[i];
+		if(!Xonomy.isNamespaceDeclaration(attr.nodeName)) {
+			if(attr.name!="xml:space") {
+				js["attributes"].push({type: "attribute", name: attr.nodeName, value: attr.value, htmlID: "", parent: function(){return js}, });
+			}
+		} else {
+			Xonomy.namespaces[attr.nodeName]=attr.value;
+		}
+	}
+	js.children=[];
+	for(var i=0; i<xml.childNodes.length; i++) {
+		var child=xml.childNodes[i];
+		if(child.nodeType==1) { //element node
+			js["children"].push(Xonomy.xml2js(child, js));
+		}
+		if(child.nodeType==3) { //text node
+			js["children"].push({type: "text", value: child.nodeValue, htmlID: "", parent: function(){return js}, });
+		}
+	}
+	js=Xonomy.enrichElement(js);
+	return js;
+};
+Xonomy.js2xml=function(js) {
+	if(js.type=="text") {
+		return Xonomy.xmlEscape(js.value);
+	} else if(js.type=="attribute") {
+		return js.name+"='"+Xonomy.xmlEscape(js.value)+"'";
+	} else if(js.type=="element") {
+		var xml="<"+js.name;
+		for(var i=0; i<js.attributes.length; i++) {
+			var att=js.attributes[i];
+			xml+=" "+att.name+"='"+Xonomy.xmlEscape(att.value)+"'";
+		}
+		if(js.children.length>0) {
+			var hasText=false;
+			for(var i=0; i<js.children.length; i++) {
+				var child=js.children[i];
+				if(child.type=="text") hasText=true;
+			}
+			if(hasText) xml+=" xml:space='preserve'";
+			xml+=">";
+			for(var i=0; i<js.children.length; i++) {
+				var child=js.children[i];
+				if(child.type=="text") xml+=Xonomy.xmlEscape(child.value); //text node
+				else if(child.type=="element") xml+=Xonomy.js2xml(child); //element node
+			}
+			xml+="</"+js.name+">";
+		} else {
+			xml+="/>";
+		}
+		return xml;
+	}
+};
+Xonomy.enrichElement=function(jsElement) {
+	jsElement.hasAttribute=function(name) {
+		var ret=false;
+		for(var i=0; i<this.attributes.length; i++) {
+			if(this.attributes[i].name==name) ret=true;
+		}
+		return ret;
+	};
+	jsElement.getAttribute=function(name) {
+		var ret=null;
+		for(var i=0; i<this.attributes.length; i++) {
+			if(this.attributes[i].name==name) ret=this.attributes[i];
+		}
+		return ret;
+	};
+	jsElement.getAttributeValue=function(name, ifNull) {
+		var ret=ifNull;
+		for(var i=0; i<this.attributes.length; i++) {
+			if(this.attributes[i].name==name) ret=this.attributes[i].value;
+		}
+		return ret;
+	};
+	jsElement.hasChildElement=function(name) {
+		var ret=false;
+		for(var i=0; i<this.children.length; i++) {
+			if(this.children[i].name==name) ret=true;
+		}
+		return ret;
+	};
+	jsElement.getChildElements=function(name) {
+		var ret=[];
+		for(var i=0; i<this.children.length; i++) {
+			if(this.children[i].type=="element") {
+				if(this.children[i].name==name) ret.push(this.children[i]);
+			}
+		}
+		return ret;
+	};
+	jsElement.getDescendantElements=function(name) {
+		var ret=[];
+		for(var i=0; i<this.children.length; i++) {
+			if(this.children[i].type=="element") {
+				if(this.children[i].name==name) ret.push(this.children[i]);
+				var temp=this.children[i].getDescendantElements(name);
+				for(var t=0; t<temp.length; t++) ret.push(temp[t]);
+			}
+		}
+		return ret;
+	};
+	jsElement.getText=function(){
+		var txt="";
+		for(var i=0; i<this.children.length; i++) {
+			if(this.children[i].type=="text") txt+=this.children[i].value;
+			else if(this.children[i].type=="element") txt+=this.children[i].getText();
+		}
+		return txt;
+	};
+	jsElement.hasElements=function(){
+		for(var i=0; i<this.children.length; i++) {
+			if(this.children[i].type=="element") return true;
+		}
+		return false;
+	};
+	jsElement.getPrecedingSibling=function(){
+		var parent=this.parent();
+		if(parent){
+			var lastSibling=null;
+			for(var i=0; i<parent.children.length; i++) {
+				if(parent.children[i].type=="element" && parent.children[i].htmlID!=this.htmlID) {
+					lastSibling=parent.children[i];
+				} else if(parent.children[i].htmlID==this.htmlID){
+					return lastSibling;
+				}
+			}
+		}
+		return null;
+	};
+	jsElement.getFollowingSibling=function(){
+		var parent=this.parent();
+		if(parent){
+			var seenSelf=false;
+			for(var i=0; i<parent.children.length; i++) {
+				if(parent.children[i].htmlID==this.htmlID){
+					seenSelf=true;
+				} else if(parent.children[i].type=="element" && seenSelf) {
+					return parent.children[i];
+				}
+			}
+		}
+		return null;
+	};
+	jsElement.setAttribute=function(name, value){
+		if(this.hasAttribute(name)){
+			this.getAttribute(name).value=value;
+		} else {
+			this.attributes.push({
+				type: "attribute",
+				name: name,
+				value: value,
+				htmlID: null,
+				parent: function(){return this;}
+			});
+		}
+	};
+	jsElement.addText=function(txt){
+		this.children.push({
+			type: "text",
+			value: txt,
+			htmlID: null,
+			parent: function(){return this;}
+		});
+	};
+	return jsElement;
+};
+
+Xonomy.verifyDocSpec=function() { //make sure the docSpec object has everything it needs
+	if(!Xonomy.docSpec || typeof(Xonomy.docSpec)!="object") Xonomy.docSpec={};
+	if(!Xonomy.docSpec.elements || typeof(Xonomy.docSpec.elements)!="object") Xonomy.docSpec.elements={};
+	if(!Xonomy.docSpec.onchange || typeof(Xonomy.docSpec.onchange)!="function") Xonomy.docSpec.onchange=function(){};
+	if(!Xonomy.docSpec.validate || typeof(Xonomy.docSpec.validate)!="function") Xonomy.docSpec.validate=function(){};
+};
+Xonomy.asFunction=function(specProperty, defaultValue){
+	if(typeof(specProperty)=="function")
+		return specProperty;
+	else if (typeof(specProperty)==typeof(defaultValue))
+		return function() { return specProperty; }
+	else
+		return function() { return defaultValue };
+}
+Xonomy.verifyDocSpecElement=function(name) { //make sure the DocSpec object has such an element, that the element has everything it needs
+	if(!Xonomy.docSpec.elements[name] || typeof(Xonomy.docSpec.elements[name])!="object") {
+		if(Xonomy.docSpec.unknownElement) {
+			Xonomy.docSpec.elements[name]=(typeof(Xonomy.docSpec.unknownElement)==="function")
+				? Xonomy.docSpec.unknownElement(name)
+				: Xonomy.docSpec.unknownElement;
+		}
+		else Xonomy.docSpec.elements[name]={};
+	}
+	var spec=Xonomy.docSpec.elements[name];
+	if(!spec.attributes || typeof(spec.attributes)!="object") spec.attributes={};
+	if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[];
+	if(!spec.inlineMenu || typeof(spec.inlineMenu)!="object") spec.inlineMenu=[];
+	if(!spec.canDropTo || typeof(spec.canDropTo)!="object") spec.canDropTo=[];
+	//if(!spec.mustBeAfter || typeof(spec.mustBeAfter)!="object") spec.mustBeAfter=[];
+	//if(!spec.mustBeBefore || typeof(spec.mustBeBefore)!="object") spec.mustBeBefore=[];
+	spec.mustBeAfter=Xonomy.asFunction(spec.mustBeAfter, []);
+	spec.mustBeBefore=Xonomy.asFunction(spec.mustBeBefore, []);
+	spec.oneliner=Xonomy.asFunction(spec.oneliner, false);
+	spec.hasText=Xonomy.asFunction(spec.hasText, false);
+	spec.collapsible=Xonomy.asFunction(spec.collapsible, true);
+	spec.collapsed=Xonomy.asFunction(spec.collapsed, false);
+	spec.localDropOnly=Xonomy.asFunction(spec.localDropOnly, false);
+	spec.isReadOnly=Xonomy.asFunction(spec.isReadOnly, false);
+	spec.isInvisible=Xonomy.asFunction(spec.isInvisible, false);
+	spec.backgroundColour=Xonomy.asFunction(spec.backgroundColour, "");
+	if(spec.displayName) spec.displayName=Xonomy.asFunction(spec.displayName, "");
+	if(spec.title) spec.title=Xonomy.asFunction(spec.title, "");
+	for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]);
+	for(var i=0; i<spec.inlineMenu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.inlineMenu[i]);
+	for(var attributeName in spec.attributes) Xonomy.verifyDocSpecAttribute(name, attributeName);
+};
+Xonomy.verifyDocSpecAttribute=function(elementName, attributeName) { //make sure the DocSpec object has such an attribute, that the attribute has everything it needs
+	var elSpec=Xonomy.docSpec.elements[elementName];
+	if(!elSpec.attributes[attributeName] || typeof(elSpec.attributes[attributeName])!="object") {
+		if(Xonomy.docSpec.unknownAttribute) {
+			elSpec.attributes[attributeName]=(typeof(Xonomy.docSpec.unknownAttribute)==="function")
+				? Xonomy.docSpec.unknownAttribute(elementName, attributeName)
+				: Xonomy.docSpec.unknownAttribute;
+		}
+		else elSpec.attributes[attributeName]={};
+	}
+	var spec=elSpec.attributes[attributeName];
+	if(!spec.asker || typeof(spec.asker)!="function") spec.asker=function(){return ""};
+	if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[];
+	spec.isReadOnly=Xonomy.asFunction(spec.isReadOnly, false);
+	spec.isInvisible=Xonomy.asFunction(spec.isInvisible, false);
+	spec.shy=Xonomy.asFunction(spec.shy, false);
+	if(spec.displayName) spec.displayName=Xonomy.asFunction(spec.displayName, "");
+	if(spec.title) spec.title=Xonomy.asFunction(spec.title, "");
+	for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]);
+};
+Xonomy.verifyDocSpecMenuItem=function(menuItem) { //make sure the menu item has all it needs
+	menuItem.caption=Xonomy.asFunction(menuItem.caption, "?");
+	if(!menuItem.action || typeof(menuItem.action)!="function") menuItem.action=function(){};
+	if(!menuItem.hideIf) menuItem.hideIf=function(){return false;};
+	if(typeof(menuItem.expanded)!="function") menuItem.expanded=Xonomy.asFunction(menuItem.expanded, false);
+};
+
+Xonomy.nextID=function() {
+	return "xonomy"+(++Xonomy.lastIDNum);
+};
+Xonomy.lastIDNum=0;
+
+Xonomy.docSpec=null;
+Xonomy.refresh=function() {
+	$(".xonomy .textnode[data-value='']").each(function(){ //delete empty text nodes if the parent element is not allowed to have text
+		var $this=$(this);
+		var $parent=$this.closest(".element");
+		var parentName=$parent.data("name");
+		var elSpec=Xonomy.docSpec.elements[parentName];
+		if(elSpec && !elSpec.hasText(Xonomy.harvestElement($parent.toArray()[0]))) {
+			$this.remove();
+		}
+	});
+	$(".xonomy .children ").each(function(){ //determine whether each element does or doesn't have children:
+		if(this.childNodes.length==0 && !$(this.parentNode).hasClass("hasText")) $(this.parentNode).addClass("noChildren");
+		else {
+			$(this.parentNode).removeClass("noChildren");
+			Xonomy.updateCollapsoid(this.parentNode.id);
+		}
+	});
+	$(".xonomy .element.hasText > .children > .element").each(function () { //determine whether each child element of hasText element should have empty text nodes on either side
+		if($(this).prev().length == 0 || !$(this).prev().hasClass("textnode")) {
+			$(this).before(Xonomy.renderText({ type: "text", value: "" }));
+		}
+		if($(this).next().length == 0 || !$(this).next().hasClass("textnode")) {
+			$(this).after(Xonomy.renderText({ type: "text", value: "" }));
+		}
+	});
+	var merged=false; while(!merged) { //merge adjacent text nodes
+		merged=true; var textnodes=$(".xonomy .textnode").toArray();
+		for(var i=0; i<textnodes.length; i++) {
+			var $this=$(textnodes[i]);
+			if($this.next().hasClass("textnode")) {
+				var js1=Xonomy.harvestText($this.toArray()[0]);
+				var js2=Xonomy.harvestText($this.next().toArray()[0]);
+				js1.value+=js2.value;
+				$this.next().remove();
+				$this.replaceWith(Xonomy.renderText(js1));
+				merged=false;
+				break;
+			}
+		}
+	}
+	$(".xonomy .attribute ").each(function(){ //reorder attributes if necessary
+		var atName=this.getAttribute("data-name");
+		var elName=this.parentNode.parentNode.parentNode.getAttribute("data-name");
+		var elSpec=Xonomy.docSpec.elements[elName];
+		var mustBeAfter=[]; for(var sibName in elSpec.attributes) {
+			if(sibName==atName) break; else mustBeAfter.push(sibName);
+		}
+		var mustBeBefore=[]; var seen=false; for(var sibName in elSpec.attributes) {
+			if(sibName==atName) seen=true; else if(seen) mustBeBefore.push(sibName);
+		}
+		if(mustBeBefore.length>0) { //is it after an attribute it cannot be after? then move it up until it's not!
+			var $this=$(this);
+			var ok; do {
+				ok=true;
+				for(var ii=0; ii<mustBeBefore.length; ii++) {
+					if( $this.prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
+						$this.prev().before($this);
+						ok=false;
+					}
+				}
+			} while(!ok)
+		}
+		if(mustBeAfter.length>0) { //is it before an attribute it cannot be before? then move it down until it's not!
+			var $this=$(this);
+			var ok; do {
+				ok=true;
+				for(var ii=0; ii<mustBeAfter.length; ii++) {
+					if( $this.nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
+						$this.next().after($this);
+						ok=false;
+					}
+				}
+			} while(!ok)
+		}
+	});
+	$(".xonomy .attributes").each(function(){ //determine whether each attribute list has any shy attributes:
+		if($(this).children(".shy").toArray().length==0) {
+			$(this.parentNode).children(".rollouter").hide().removeClass("rolledout");
+			$(this).removeClass("rolledout").css("display", "");
+		} else {
+			$(this.parentNode).children(".rollouter").show();
+		}
+	});
+	$(".xonomy .element").each(function(){ //refresh display names, display values and captions:
+		var elSpec=Xonomy.docSpec.elements[this.getAttribute("data-name")];
+		if(elSpec.displayName) $(this).children(".tag").children(".name").html(Xonomy.textByLang(elSpec.displayName(Xonomy.harvestElement(this))));
+		if(elSpec.caption) {
+			var jsEl=Xonomy.harvestElement(this);
+			$(this).children(".inlinecaption").html(Xonomy.textByLang(elSpec.caption(jsEl)));
+		}
+		if(elSpec.displayValue) {
+			var jsEl=Xonomy.harvestElement(this);
+			if(!jsEl.hasElements()) $(this).children(".children").html(  Xonomy.textByLang(Xonomy.renderDisplayText(jsEl.getText(), elSpec.displayValue(jsEl))) );
+		}
+		$(this).children(".tag.opening").children(".attributes").children(".attribute").each(function(){
+			var atSpec=elSpec.attributes[this.getAttribute("data-name")];
+			if(atSpec.displayName) $(this).children(".name").html(Xonomy.textByLang(atSpec.displayName(Xonomy.harvestAttribute(this))));
+			if(atSpec.displayValue) $(this).children(".value").html(Xonomy.textByLang(atSpec.displayValue(Xonomy.harvestAttribute(this))));
+			if(atSpec.caption) $(this).children(".inlinecaption").html("&nbsp;"+Xonomy.textByLang(atSpec.caption(Xonomy.harvestAttribute(this)))+"&nbsp;");
+		});
+	});
+};
+
+Xonomy.harvestCache={};
+Xonomy.harvest=function() { //harvests the contents of an editor
+	//Returns xml-as-string.
+	var rootElement=$(".xonomy .element").first().toArray()[0];
+	var js=Xonomy.harvestElement(rootElement);
+	for(var key in Xonomy.namespaces) {
+		if(!js.hasAttribute(key)) js.attributes.push({
+			type: "attribute",
+			name: key,
+			value: Xonomy.namespaces[key],
+			parent: js
+		});
+	}
+	return Xonomy.js2xml(js);
+}
+Xonomy.harvestElement=function(htmlElement, jsParent) {
+	var htmlID=htmlElement.id;
+	if(!Xonomy.harvestCache[htmlID]) {
+		var js=new Xonomy.surrogate(jsParent);
+		js.type="element";
+		js.name=htmlElement.getAttribute("data-name");
+		js.htmlID=htmlElement.id;
+		js.attributes=[];
+		var htmlAttributes=$(htmlElement).find(".tag.opening > .attributes").toArray()[0];
+		for(var i=0; i<htmlAttributes.childNodes.length; i++) {
+			var htmlAttribute=htmlAttributes.childNodes[i];
+			if($(htmlAttribute).hasClass("attribute")) js["attributes"].push(Xonomy.harvestAttribute(htmlAttribute, js));
+		}
+		js.children=[];
+		var htmlChildren=$(htmlElement).children(".children").toArray()[0];
+		for(var i=0; i<htmlChildren.childNodes.length; i++) {
+			var htmlChild=htmlChildren.childNodes[i];
+			if($(htmlChild).hasClass("element")) js["children"].push(Xonomy.harvestElement(htmlChild, js));
+			else if($(htmlChild).hasClass("textnode")) js["children"].push(Xonomy.harvestText(htmlChild, js));
+		}
+		js=Xonomy.enrichElement(js);
+		Xonomy.harvestCache[htmlID]=js;
+	}
+	return Xonomy.harvestCache[htmlID];
+};
+Xonomy.harvestAttribute=function(htmlAttribute, jsParent) {
+	var htmlID=htmlAttribute.id;
+	if(!Xonomy.harvestCache[htmlID]) {
+		var js = new Xonomy.surrogate(jsParent);
+		js.type = "attribute";
+		js.name = htmlAttribute.getAttribute("data-name");
+		js.htmlID = htmlAttribute.id;
+		js.value = htmlAttribute.getAttribute("data-value");
+		Xonomy.harvestCache[htmlID]=js;
+	}
+	return Xonomy.harvestCache[htmlID];
+}
+
+Xonomy.surrogate=function(jsParent) {
+	this.internalParent=jsParent;
+}
+Xonomy.surrogate.prototype.parent=function() {
+	if (!this.internalParent) {
+		this.internalParent=Xonomy.harvestParentOf(this);
+	}
+	return this.internalParent;
+}
+
+Xonomy.harvestText = function (htmlText, jsParent) {
+	var js = new Xonomy.surrogate(jsParent);
+	js.type = "text";
+	js.htmlID = htmlText.id;
+	js.value = htmlText.getAttribute("data-value");
+	return js;
+}
+Xonomy.harvestParentOf=function(js) {
+	var jsParent=null;
+	var $parent=$("#"+js.htmlID).parent().closest(".element");
+	if($parent.toArray().length==1) {
+		jsParent=Xonomy.harvestElement($parent.toArray()[0]);
+		for(var i=0; i<jsParent.attributes.length; i++) if(jsParent.attributes[i].htmlID==js.htmlID) jsParent.attributes[i]=js;
+		for(var i=0; i<jsParent.children.length; i++) if(jsParent.children[i].htmlID==js.htmlID) jsParent.children[i]=js;
+	}
+	return jsParent;
+};
+
+Xonomy.render=function(data, editor, docSpec) { //renders the contents of an editor
+	//The data can be a Xonomy-compliant XML document, a Xonomy-compliant xml-as-string,
+	//or a Xonomy-compliant JavaScript object.
+	//The editor can be an HTML element, or the string ID of one.
+	Xonomy.docSpec=docSpec;
+	Xonomy.verifyDocSpec();
+
+	//Clear namespace cache:
+	Xonomy.namespaces={};
+
+	//Convert doc to a JavaScript object, if it isn't a JavaScript object already:
+	if(typeof(data)=="string") data=$.parseXML(data);
+	if(data.documentElement) data=Xonomy.xml2js(data);
+
+	//Make sure editor refers to an HTML element, if it doesn't already:
+	if(typeof(editor)=="string") editor=document.getElementById(editor);
+	if(!$(editor).hasClass("xonomy")) $(editor).addClass("xonomy"); //make sure it has class "xonomy"
+	$(editor).addClass(Xonomy.mode);
+
+	$(editor).hide();
+	editor.innerHTML=Xonomy.renderElement(data, editor);
+	$(editor).show();
+
+	if(docSpec.allowLayby){
+		var laybyHtml="<div class='layby closed empty' onclick='if($(this).hasClass(\"closed\")) Xonomy.openLayby()' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)''>";
+		laybyHtml+="<span class='button closer' onclick='Xonomy.closeLayby();'>&nbsp;</span>";
+		laybyHtml+="<span class='button purger' onclick='Xonomy.emptyLayby()'>&nbsp;</span>";
+		laybyHtml+="<div class='content'></div>";
+		laybyHtml+="<div class='message'>"+Xonomy.textByLang(docSpec.laybyMessage)+"</div>";
+		laybyHtml+="</div>";
+		$(laybyHtml).appendTo($(editor));
+	}
+
+	if(docSpec.allowModeSwitching){
+		$("<div class='modeSwitcher'><span class='nerd'></span><span class='laic'></span></div>").appendTo($(editor)).on("click", function(e){
+			if(Xonomy.mode=="nerd") { Xonomy.setMode("laic"); } else { Xonomy.setMode("nerd"); }
+			if(docSpec.onModeSwitch) docSpec.onModeSwitch(Xonomy.mode);
+		});
+	}
+
+	//Make sure the "click off" handler is attached:
+	$(document.body).off("click", Xonomy.clickoff);
+	$(document.body).on("click", Xonomy.clickoff);
+
+	//Make sure the "drag end" handler is attached:
+	$(document.body).off("dragend", Xonomy.dragend);
+	$(document.body).on("dragend", Xonomy.dragend);
+
+	Xonomy.refresh();
+	Xonomy.validate();
+};
+Xonomy.renderElement=function(element) {
+	var htmlID=Xonomy.nextID();
+	Xonomy.verifyDocSpecElement(element.name);
+	var spec=Xonomy.docSpec.elements[element.name];
+	var classNames="element";
+	if(spec.canDropTo && spec.canDropTo.length>0) classNames+=" draggable";
+	var hasText = spec.hasText(element);
+	if(hasText) classNames+=" hasText";
+	if(spec.inlineMenu && spec.inlineMenu.length>0) classNames+=" hasInlineMenu";
+	if(spec.oneliner(element)) classNames+=" oneliner";
+	if(!spec.collapsible(element)) {
+		classNames+=" uncollapsible";
+	} else {
+		if(spec.collapsed(element) && element.children.length>0) classNames+=" collapsed";
+	}
+	if(spec.isInvisible && spec.isInvisible(element)) { classNames+=" invisible"; }
+	if(spec.isReadOnly && spec.isReadOnly(element)) { readonly=true; classNames+=" readonly"; }
+	if(spec.menu.length>0) classNames+=" hasMenu"; //not always accurate: whether an element has a menu is actually determined at runtime
+	var displayName=element.name;
+	if(spec.displayName) displayName=Xonomy.textByLang(spec.displayName(element));
+	var title="";
+	if(spec.title) title=Xonomy.textByLang(spec.title(element));
+	var html="";
+	html+='<div data-name="'+element.name+'" id="'+htmlID+'" class="'+classNames+'">';
+		html+='<span class="connector">';
+			html+='<span class="plusminus" onclick="Xonomy.plusminus(\''+htmlID+'\')"></span>';
+			html+='<span class="draghandle" draggable="true" ondragstart="Xonomy.drag(event)"></span>';
+		html+='</span>';
+		html+='<span class="tag opening focusable" style="background-color: '+spec.backgroundColour(element)+';">';
+			html+='<span class="punc">&lt;</span>';
+			html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>';
+			html+='<span class="name" title="'+title+'" onclick="Xonomy.click(\''+htmlID+'\', \'openingTagName\')">'+displayName+'</span>';
+			html+='<span class="attributes">';
+				for(var i=0; i<element.attributes.length; i++) {
+					Xonomy.verifyDocSpecAttribute(element.name, element.attributes[i].name);
+					html+=Xonomy.renderAttribute(element.attributes[i], element.name);
+				}
+			html+='</span>';
+			html+='<span class="rollouter focusable" onclick="Xonomy.click(\''+htmlID+'\', \'rollouter\')"></span>';
+			html+='<span class="punc slash">/</span>';
+			html+='<span class="punc">&gt;</span>';
+		html+='</span>';
+		if(spec.caption && !spec.oneliner(element)) html+="<span class='inlinecaption'>"+Xonomy.textByLang(spec.caption(element))+"</span>";
+		html+='<span class="childrenCollapsed focusable" onclick="Xonomy.plusminus(\''+htmlID+'\', true)">&middot;&middot;&middot;</span>';
+		html+='<div class="children">';
+			if(spec.displayValue && !element.hasElements()) {
+				html+=Xonomy.renderDisplayText(element.getText(), spec.displayValue(element));
+			} else {
+				var prevChildType="";
+				if(hasText && (element.children.length==0 || element.children[0].type=="element")) {
+					html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
+				}
+				for(var i=0; i<element.children.length; i++) {
+					var child=element.children[i];
+					if(hasText && prevChildType=="element" && child.type=="element") {
+						html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
+					}
+					if(child.type=="text") html+=Xonomy.renderText(child); //text node
+					else if(child.type=="element") html+=Xonomy.renderElement(child); //element node
+					prevChildType=child.type;
+				}
+				if(hasText && element.children.length>1 && element.children[element.children.length-1].type=="element") {
+					html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
+				}
+			}
+		html+='</div>';
+		html+='<span class="tag closing focusable" style="background-color: '+spec.backgroundColour(element)+';">';
+			html+='<span class="punc">&lt;</span>';
+			html+='<span class="punc">/</span>';
+			html+='<span class="name" onclick="Xonomy.click(\''+htmlID+'\', \'closingTagName\')">'+displayName+'</span>';
+			html+='<span class="punc">&gt;</span>';
+		html+='</span>';
+		if(spec.caption && spec.oneliner(element)) html+="<span class='inlinecaption'>"+Xonomy.textByLang(spec.caption(element))+"</span>";
+	html+='</div>';
+	element.htmlID = htmlID;
+	return html;
+};
+Xonomy.renderAttribute=function(attribute, optionalParentName) {
+	var htmlID=Xonomy.nextID();
+	classNames="attribute";
+	var readonly=false;
+
+	var displayName=attribute.name;
+	var displayValue=Xonomy.xmlEscape(attribute.value);
+	var caption="";
+	var title="";
+	if(optionalParentName) {
+		var spec=Xonomy.docSpec.elements[optionalParentName].attributes[attribute.name];
+		if(spec) {
+			if(spec.displayName) displayName=Xonomy.textByLang(spec.displayName(attribute));
+			if(spec.displayValue) displayValue=Xonomy.textByLang(spec.displayValue(attribute));
+			if(spec.title) title=Xonomy.textByLang(spec.title(attribute));
+			if(spec.caption) caption=Xonomy.textByLang(spec.caption(attribute));
+			if(spec.isReadOnly && spec.isReadOnly(attribute)) { readonly=true; classNames+=" readonly"; }
+			if(spec.isInvisible && spec.isInvisible(attribute)) { classNames+=" invisible"; }
+			if(spec.shy && spec.shy(attribute)) { classNames+=" shy"; }
+		}
+	}
+
+	var html="";
+	html+='<span data-name="'+attribute.name+'" data-value="'+Xonomy.xmlEscape(attribute.value)+'" id="'+htmlID+'" class="'+classNames+'">';
+		html+='<span class="punc"> </span>';
+		var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeName\')"';
+		html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>';
+		html+='<span class="name attributeName focusable" title="'+title+'"'+onclick+'>'+displayName+'</span>';
+		html+='<span class="punc">=</span>';
+		var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeValue\')"';
+		html+='<span class="valueContainer attributeValue focusable"'+onclick+'>';
+			html+='<span class="punc">"</span>';
+			html+='<span class="value">'+displayValue+'</span>';
+			html+='<span class="punc">"</span>';
+		html+='</span>';
+		if(caption) html+="<span class='inlinecaption'>"+caption+"</span>";
+	html+='</span>';
+	attribute.htmlID = htmlID;
+	return html;
+};
+Xonomy.renderText=function(text) {
+	var htmlID=Xonomy.nextID();
+	var classNames="textnode focusable";
+	if($.trim(text.value)=="") classNames+=" whitespace";
+	if(text.value=="") classNames+=" empty";
+	var html="";
+	html+='<div id="'+htmlID+'" data-value="'+Xonomy.xmlEscape(text.value)+'" class="'+classNames+'">';
+		html+='<span class="connector"></span>';
+		var txt=Xonomy.chewText(text.value);
+		html+='<span class="value" onclick="Xonomy.click(\''+htmlID+'\', \'text\')"><span class="insertionPoint"><span class="inside"></span></span><span class="dots"></span>'+txt+'</span>';
+	html+='</div>';
+	text.htmlID = htmlID;
+	return html;
+}
+Xonomy.renderDisplayText=function(text, displayText) {
+	var htmlID=Xonomy.nextID();
+	var classNames="textnode";
+	if($.trim(displayText)=="") classNames+=" whitespace";
+	if(displayText=="") classNames+=" empty";
+	var html="";
+	html+='<div id="'+htmlID+'" data-value="'+Xonomy.xmlEscape(text)+'" class="'+classNames+'">';
+		html+='<span class="connector"></span>';
+		html+='<span class="value" onclick="Xonomy.click(\''+htmlID+'\', \'text\')"><span class="insertionPoint"><span class="inside"></span></span><span class="dots"></span>'+Xonomy.textByLang(displayText)+'</span>';
+	html+='</div>';
+	text.htmlID = htmlID;
+	return html;
+}
+
+Xonomy.chewText=function(txt) {
+	var ret="";
+	ret+="<span class='word'>"; //start word
+	for(var i=0; i<txt.length; i++) {
+		if(txt[i]==" ") ret+="</span>"; //end word
+		var t=Xonomy.xmlEscape(txt[i])
+		if(i==0 && t==" ") t="<span class='space'>&middot;</span>"; //leading space
+		if(i==txt.length-1 && t==" ") t="<span class='space'>&middot;</span>"; //trailing space
+		var id=Xonomy.nextID();
+		ret+="<span id='"+id+"' class='char focusable' onclick='if((event.ctrlKey||event.metaKey) && $(this).closest(\".element\").hasClass(\"hasInlineMenu\")) Xonomy.charClick(this)'>"+t+"<span class='selector'><span class='inside' onclick='Xonomy.charClick(this.parentNode.parentNode)'></span></span></span>";
+		if(txt[i]==" ") ret+="<span class='word'>"; //start word
+	}
+	ret+="</span>"; //end word
+	return ret;
+};
+Xonomy.charClick=function(c) {
+	Xonomy.clickoff();
+	var isReadOnly=( $(c).closest(".readonly").toArray().length>0 );
+	if(!isReadOnly) {
+		Xonomy.notclick=true;
+		if(
+			$(".xonomy .char.on").toArray().length==1 && //if there is precisely one previously selected character
+			$(".xonomy .char.on").closest(".element").is($(c).closest(".element")) //and if it has the same parent element as this character
+		) {
+			var $element=$(".xonomy .char.on").closest(".element");
+			var chars=$element.find(".char").toArray();
+			var iFrom=$.inArray($(".xonomy .char.on").toArray()[0], chars);
+			var iTill=$.inArray(c, chars);
+			if(iFrom>iTill) {var temp=iFrom; iFrom=iTill; iTill=temp;}
+			for(var i=0; i<chars.length; i++) { //highlight all chars between start and end
+				if(i>=iFrom && i<=iTill) $(chars[i]).addClass("on");
+			}
+			//Save for later the info Xonomy needs to know what to wrap:
+			Xonomy.textFromID=$(chars[iFrom]).closest(".textnode").attr("id");
+			Xonomy.textTillID=$(chars[iTill]).closest(".textnode").attr("id");
+			Xonomy.textFromIndex=$.inArray(chars[iFrom], $("#"+Xonomy.textFromID).find(".char").toArray());
+			Xonomy.textTillIndex=$.inArray(chars[iTill], $("#"+Xonomy.textTillID).find(".char").toArray());
+			//Show inline menu etc:
+			var htmlID=$element.attr("id");
+			var content=Xonomy.inlineMenu(htmlID); //compose bubble content
+			if(content!="" && content!="<div class='menu'></div>") {
+				document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+				Xonomy.showBubble($("#"+htmlID+" .char.on").last()); //anchor bubble to highlighted chars
+			}
+			Xonomy.clearChars=true;
+		} else {
+			$(".xonomy .char.on").removeClass("on");
+			$(c).addClass("on");
+			Xonomy.setFocus(c.id, "char");
+		}
+	}
+};
+Xonomy.wrap=function(htmlID, param) {
+	Xonomy.clickoff();
+	Xonomy.destroyBubble();
+	var xml=param.template;
+	var ph=param.placeholder;
+	var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
+	if(Xonomy.textFromID==Xonomy.textTillID) { //abc --> a<XYZ>b</XYZ>c
+		var jsOld=Xonomy.harvestText(document.getElementById(Xonomy.textFromID));
+		var txtOpen=jsOld.value.substring(0, Xonomy.textFromIndex);
+		var txtMiddle=jsOld.value.substring(Xonomy.textFromIndex, Xonomy.textTillIndex+1);
+		var txtClose=jsOld.value.substring(Xonomy.textTillIndex+1);
+		xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddle));
+		var html="";
+		html+=Xonomy.renderText({type: "text", value: txtOpen});
+		var js=Xonomy.xml2js(xml, jsElement); html+=Xonomy.renderElement(js); var newID=js.htmlID;
+		html+=Xonomy.renderText({type: "text", value: txtClose});
+		$("#"+Xonomy.textFromID).replaceWith(html);
+		window.setTimeout(function(){ Xonomy.setFocus(newID, "openingTagName"); }, 100);
+	} else { //ab<...>cd --> a<XYZ>b<...>c</XYZ>d
+		var jsOldOpen=Xonomy.harvestText(document.getElementById(Xonomy.textFromID));
+		var jsOldClose=Xonomy.harvestText(document.getElementById(Xonomy.textTillID));
+		var txtOpen=jsOldOpen.value.substring(0, Xonomy.textFromIndex);
+		var txtMiddleOpen=jsOldOpen.value.substring(Xonomy.textFromIndex);
+		var txtMiddleClose=jsOldClose.value.substring(0, Xonomy.textTillIndex+1);
+		var txtClose=jsOldClose.value.substring(Xonomy.textTillIndex+1);
+		xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleOpen)+ph);
+		$("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).each(function(){
+			if($(this).hasClass("element")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestElement(this))+ph);
+			else if($(this).hasClass("textnode")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestText(this))+ph);
+		});
+		xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleClose));
+		$("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).remove();
+		$("#"+Xonomy.textTillID).remove();
+		var html="";
+		html+=Xonomy.renderText({type: "text", value: txtOpen});
+		var js=Xonomy.xml2js(xml, jsElement); html+=Xonomy.renderElement(js); var newID=js.htmlID;
+		html+=Xonomy.renderText({type: "text", value: txtClose});
+		$("#"+Xonomy.textFromID).replaceWith(html);
+		window.setTimeout(function(){ Xonomy.setFocus(newID, "openingTagName"); }, 100);
+	}
+	Xonomy.changed();
+};
+Xonomy.unwrap=function(htmlID, param) {
+	var parentID=$("#"+htmlID)[0].parentNode.parentNode.id;
+	Xonomy.clickoff();
+	$("#"+htmlID).replaceWith($("#"+htmlID+" > .children > *"));
+	Xonomy.changed();
+	window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName");  }, 100);
+};
+
+Xonomy.plusminus=function(htmlID, forceExpand) {
+	var $element=$("#"+htmlID);
+	var $children=$element.children(".children");
+	if($element.hasClass("collapsed")) {
+		$children.hide();
+		$element.removeClass("collapsed");
+		if($element.hasClass("oneliner")) $children.fadeIn("fast"); else $children.slideDown("fast");
+	} else if(!forceExpand) {
+		Xonomy.updateCollapsoid(htmlID);
+		if($element.hasClass("oneliner")) $children.fadeOut("fast", function(){ $element.addClass("collapsed"); });
+		else $children.slideUp("fast", function(){ $element.addClass("collapsed"); });
+	}
+	window.setTimeout(function(){
+		if($("#"+Xonomy.currentHtmlId+" .opening:visible").length>0) {
+			Xonomy.setFocus(Xonomy.currentHtmlId, "openingTagName");
+		} else {
+			Xonomy.setFocus(Xonomy.currentHtmlId, "childrenCollapsed");
+		}
+	}, 300);
+};
+Xonomy.updateCollapsoid=function(htmlID) {
+	var $element=$("#"+htmlID);
+	var whisper="";
+	var elementName=$element.data("name");
+	var spec=Xonomy.docSpec.elements[elementName];
+	if(spec.collapsoid) {
+		whisper=spec.collapsoid(Xonomy.harvestElement($element.toArray()[0]));
+	} else {
+		var abbreviated=false;
+		$element.find(".textnode").each(function(){
+			var txt=Xonomy.harvestText(this).value;
+			for(var i=0; i<txt.length; i++) {
+				if(whisper.length<35) whisper+=txt[i]; else abbreviated=true;
+			}
+			whisper+=" ";
+		});
+		whisper=whisper.replace(/  +/g, " ").replace(/ +$/g, "");
+		if(abbreviated && !$element.hasClass("oneliner") && whisper!="...") whisper+="...";
+	}
+	if(whisper=="" || !whisper) whisper="...";
+	$element.children(".childrenCollapsed").html(whisper);
+};
+
+Xonomy.lastClickWhat="";
+Xonomy.click=function(htmlID, what) {
+	if(!Xonomy.notclick) {
+		Xonomy.clickoff();
+		Xonomy.lastClickWhat=what;
+		Xonomy.currentHtmlId=htmlID;
+		Xonomy.currentFocus=what;
+		$(".xonomy .char.on").removeClass("on");
+		var isReadOnly=( $("#"+htmlID).hasClass("readonly") || $("#"+htmlID).closest(".readonly").toArray().length>0 );
+		if(!isReadOnly && (what=="openingTagName" || what=="closingTagName") ) {
+			$("#"+htmlID).addClass("current"); //make the element current
+			var content=Xonomy.elementMenu(htmlID); //compose bubble content
+			if(content!="" && content!="<div class='menu'></div>") {
+				document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+				if(what=="openingTagName") Xonomy.showBubble($("#"+htmlID+" > .tag.opening > .name")); //anchor bubble to opening tag
+				if(what=="closingTagName") Xonomy.showBubble($("#"+htmlID+" > .tag.closing > .name")); //anchor bubble to closing tag
+			}
+			var surrogateElem = Xonomy.harvestElement(document.getElementById(htmlID));
+			$("#"+htmlID).trigger("xonomy-click-element", [surrogateElem]);
+		}
+		if(!isReadOnly && what=="attributeName") {
+			$("#"+htmlID).addClass("current"); //make the attribute current
+			var content=Xonomy.attributeMenu(htmlID); //compose bubble content
+			if(content!="" && content!="<div class='menu'></div>") {
+				document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+				Xonomy.showBubble($("#"+htmlID+" > .name")); //anchor bubble to attribute name
+			}
+			var surrogateAttr = Xonomy.harvestAttribute(document.getElementById(htmlID));
+			$("#"+htmlID).trigger("xonomy-click-attribute", [surrogateAttr]);
+		}
+		if(!isReadOnly && what=="attributeValue") {
+			$("#"+htmlID+" > .valueContainer").addClass("current"); //make attribute value current
+			var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name
+			var value=$("#"+htmlID).attr("data-value"); //obtain current value
+			var elName=$("#"+htmlID).closest(".element").attr("data-name");
+			Xonomy.verifyDocSpecAttribute(elName, name);
+			var spec=Xonomy.docSpec.elements[elName].attributes[name];
+			var content=spec.asker(value, spec.askerParameter, Xonomy.harvestAttribute(document.getElementById(htmlID))); //compose bubble content
+			if(content!="" && content!="<div class='menu'></div>") {
+				document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+				Xonomy.showBubble($("#"+htmlID+" > .valueContainer > .value")); //anchor bubble to value
+				Xonomy.answer=function(val) {
+					var obj=document.getElementById(htmlID);
+					var html=Xonomy.renderAttribute({type: "attribute", name: name, value: val}, elName);
+					$(obj).replaceWith(html);
+					Xonomy.changed();
+					window.setTimeout(function(){Xonomy.clickoff(); Xonomy.setFocus($(html).prop("id"), what)}, 100);
+				};
+			}
+		}
+		if(!isReadOnly && what=="text") {
+			$("#"+htmlID).addClass("current");
+			var value=$("#"+htmlID).attr("data-value"); //obtain current value
+			var elName=$("#"+htmlID).closest(".element").attr("data-name");
+			var spec=Xonomy.docSpec.elements[elName];
+			if (typeof(spec.asker) != "function") {
+				var content=Xonomy.askLongString(value, null, Xonomy.harvestElement($("#"+htmlID).closest(".element").toArray()[0])); //compose bubble content
+			} else {
+				var content=spec.asker(value, spec.askerParameter, Xonomy.harvestElement($("#"+htmlID).closest(".element").toArray()[0])); //use specified asker
+			}
+			if(content!="" && content!="<div class='menu'></div>") {
+				document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+				Xonomy.showBubble($("#"+htmlID+" > .value")); //anchor bubble to value
+				Xonomy.answer=function(val) {
+					var obj=document.getElementById(htmlID);
+					var jsText = {type: "text", value: val};
+					var html=Xonomy.renderText(jsText);
+					$(obj).replaceWith(html);
+					Xonomy.changed(Xonomy.harvestText(document.getElementById(jsText.htmlID)));
+					window.setTimeout(function(){Xonomy.clickoff(); Xonomy.setFocus($(html).prop("id"), what)}, 100);
+				};
+			}
+		}
+		if(what=="warner") {
+			//$("#"+htmlID).addClass("current");
+			var content=""; //compose bubble content
+			for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) {
+				var warning=Xonomy.warnings[iWarning];
+				if(warning.htmlID==htmlID) {
+					content+="<div class='warning'>"+Xonomy.formatCaption(Xonomy.textByLang(warning.text))+"</div>";
+				}
+			}
+			document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
+			Xonomy.showBubble($("#"+htmlID+" .warner .inside").first()); //anchor bubble to warner
+		}
+		if(what=="rollouter" && $("#"+htmlID+" > .tag.opening > .attributes").children(".shy").toArray().length>0) {
+			if( $("#"+htmlID).children(".tag.opening").children(".rollouter").hasClass("rolledout") ) {
+				$("#"+htmlID).children(".tag.opening").children(".rollouter").removeClass("rolledout");
+				$("#"+htmlID).children(".tag.opening").children(".attributes").slideUp("fast", function(){
+					$(this).removeClass("rolledout").css("display", "");
+				})
+			} else {
+				$("#"+htmlID).children(".tag.opening").children(".rollouter").addClass("rolledout");
+				$("#"+htmlID).children(".tag.opening").children(".attributes").addClass("rolledout").hide().slideDown("fast");
+			}
+			window.setTimeout(function(){Xonomy.setFocus(htmlID, "rollouter")}, 100);
+		}
+		Xonomy.notclick=true;
+	}
+};
+Xonomy.notclick=false; //should the latest click-off event be ignored?
+Xonomy.clearChars=false; //if true, un-highlight any highlighted characters at the next click-off event
+Xonomy.clickoff=function() { //event handler for the document-wide click-off event.
+	if(!Xonomy.notclick) {
+		Xonomy.currentHtmlId=null;
+		Xonomy.currentFocus=null;
+		Xonomy.destroyBubble();
+		$(".xonomy .current").removeClass("current");
+		$(".xonomy .focused").removeClass("focused");
+		if(Xonomy.clearChars) {
+			$(".xonomy .char.on").removeClass("on");
+			Xonomy.clearChars=false;
+		}
+	}
+	Xonomy.notclick=false;
+};
+
+Xonomy.destroyBubble=function() {
+	if(document.getElementById("xonomyBubble")) {
+		var bubble=document.getElementById("xonomyBubble");
+		$(bubble).find(":focus").blur();
+		bubble.parentNode.removeChild(bubble);
+		if(Xonomy.keyboardEventCatcher) Xonomy.keyboardEventCatcher.focus();
+	}
+};
+Xonomy.makeBubble=function(content) {
+	Xonomy.destroyBubble();
+	var bubble=document.createElement("div");
+	bubble.id="xonomyBubble";
+	bubble.className=Xonomy.mode;
+	bubble.innerHTML="<div class='inside' onclick='Xonomy.notclick=true;'>"
+			+"<div id='xonomyBubbleContent'>"+content+"</div>"
+		+"</div>";
+	return bubble;
+};
+Xonomy.showBubble=function($anchor) {
+	var $bubble=$("#xonomyBubble");
+	var offset=$anchor.offset();
+	var screenWidth = $("body").width();
+	var screenHeight = $(document).height();
+	var bubbleHeight = $bubble.outerHeight();
+	var width = $anchor.width(); if (width > 40) width = 40;
+	var height = $anchor.height(); if (height > 25) height = 25;
+	if (Xonomy.mode == "laic") { width = width - 25; height = height + 10; }
+
+	function verticalPlacement() {
+		var top = "";
+		var bottom = "";
+		if (offset.top + height + bubbleHeight <= screenHeight) {
+			// enough space - open down
+			top = (offset.top + height) + "px";
+		} else if (screenHeight - offset.top + 5 + bubbleHeight > 0) {
+			// 5px above for some padding. Anchor using bottom so animation opens upwards.
+			bottom = (screenHeight - offset.top + 5) + "px";
+		} else {
+			// neither downwards nor upwards is enough space => center the bubble
+			top = (screenHeight - bubbleHeight)/2 + "px";
+		}
+		return { top: top, bottom: bottom };
+	}
+
+	var placement = verticalPlacement();
+	if(offset.left<screenWidth/2) {
+		placement.left = (offset.left + width - 15) + "px";
+	} else {
+		$bubble.addClass("rightAnchored");
+		placement.right = (screenWidth - offset.left) + "px";
+	}
+	$bubble.css(placement);
+	$bubble.slideDown("fast", function() {
+		if(Xonomy.keyNav) $bubble.find(".focusme").first().focus(); //if the context menu contains anything with the class name 'focusme', focus it.
+		else $bubble.find("input.focusme, select.focusme, textarea.focusme").first().focus();
+	});
+
+	$bubble.on("keyup", function(event){
+		if(event.which==27) Xonomy.destroyBubble();
+	});
+
+	if(Xonomy.keyNav) {
+		$bubble.find("div.focusme").on("keyup", function(event){
+			if(event.which==40) { //down key
+				var $item=$(event.delegateTarget);
+				var $items=$bubble.find(".focusme:visible");
+				var $next=$items.eq( $items.index($item[0])+1 );
+				$next.focus();
+			}
+			if(event.which==38) { //up key
+				var $item=$(event.delegateTarget);
+				var $items=$bubble.find("div.focusme:visible");
+				var $next=$items.eq( $items.index($item[0])-1 );
+				$next.focus();
+			}
+			if(event.which==13) { //enter key
+				$(event.delegateTarget).click();
+				Xonomy.notclick=false;
+			}
+		});
+	}
+};
+
+Xonomy.askString=function(defaultString, askerParameter, jsMe) {
+	var width=($("body").width()*.5)-75
+	var html="";
+	html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>";
+		html+="<input name='val' class='textbox focusme' style='width: "+width+"px;' value='"+Xonomy.xmlEscape(defaultString)+"' onkeyup='Xonomy.notKeyUp=true'/>";
+		html+=" <input type='submit' value='OK'>";
+	html+="</form>";
+	return html;
+};
+Xonomy.askLongString=function(defaultString, askerParameter, jsMe) {
+	var width=($("body").width()*.5)-75
+	var html="";
+	html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>";
+		html+="<textarea name='val' class='textbox focusme' spellcheck='false' style='width: "+width+"px; height: 150px;'>"+Xonomy.xmlEscape(defaultString)+"</textarea>";
+		html+="<div class='submitline'><input type='submit' value='OK'></div>";
+	html+="</form>";
+	return html;
+};
+Xonomy.askPicklist=function(defaultString, picklist, jsMe) {
+	var html="";
+	html+=Xonomy.pickerMenu(picklist, defaultString);
+	return html;
+};
+Xonomy.askOpenPicklist=function(defaultString, picklist) {
+	var isInPicklist=false;
+    var html="";
+		html+=Xonomy.pickerMenu(picklist, defaultString);
+		html+="<form class='undermenu' onsubmit='Xonomy.answer(this.val.value); return false'>";
+		html+="<input name='val' class='textbox focusme' value='"+(!isInPicklist ? Xonomy.xmlEscape(defaultString) : "")+"' onkeyup='Xonomy.notKeyUp=true'/>";
+		html+=" <input type='submit' value='OK'>";
+		html+="</form>";
+    return html;
+};
+Xonomy.askRemote=function(defaultString, param, jsMe) {
+	var html="";
+	if(param.searchUrl || param.createUrl) {
+		html+="<form class='overmenu' onsubmit='return Xonomy.remoteSearch(\""+Xonomy.xmlEscape(param.searchUrl, true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>";
+		html+="<input name='val' class='textbox focusme' value=''/>";
+		if(param.searchUrl) html+=" <button class='buttonSearch' onclick='return Xonomy.remoteSearch(\""+Xonomy.xmlEscape(param.searchUrl, true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>&nbsp;</button>";
+		if(param.createUrl) html+=" <button class='buttonCreate' onclick='return Xonomy.remoteCreate(\""+Xonomy.xmlEscape(param.createUrl, true)+"\", \""+Xonomy.xmlEscape( (param.searchUrl?param.searchUrl:param.url) , true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>&nbsp;</button>";
+		html+="</form>";
+	}
+	html+=Xonomy.wyc(param.url, function(picklist){
+		var items=[];
+		if(param.add) for(var i=0; i<param.add.length; i++) items.push(param.add[i]);
+		for(var i=0; i<picklist.length; i++) items.push(picklist[i]);
+		return Xonomy.pickerMenu(items, defaultString);
+	});
+	Xonomy.lastAskerParam=param;
+	return html;
+};
+Xonomy.lastAskerParam=null;
+Xonomy.remoteSearch=function(searchUrl, urlPlaceholder, defaultString){
+	var text=$("#xonomyBubble input.textbox").val();
+	searchUrl=searchUrl.replace(urlPlaceholder, encodeURIComponent(text));
+	$("#xonomyBubble .menu").replaceWith( Xonomy.wyc(searchUrl, function(picklist){
+		var items=[];
+		if(text=="" && Xonomy.lastAskerParam.add) for(var i=0; i<Xonomy.lastAskerParam.add.length; i++) items.push(Xonomy.lastAskerParam.add[i]);
+		for(var i=0; i<picklist.length; i++) items.push(picklist[i]);
+		return Xonomy.pickerMenu(items, defaultString);
+	}));
+	return false;
+};
+Xonomy.remoteCreate=function(createUrl, searchUrl, urlPlaceholder, defaultString){
+	var text=$.trim($("#xonomyBubble input.textbox").val());
+	if(text!="") {
+		createUrl=createUrl.replace(urlPlaceholder, encodeURIComponent(text));
+		searchUrl=searchUrl.replace(urlPlaceholder, encodeURIComponent(text));
+		$.ajax({url: createUrl, dataType: "text", method: "POST"}).done(function(data){
+			if(Xonomy.wycCache[searchUrl]) delete Xonomy.wycCache[searchUrl];
+			$("#xonomyBubble .menu").replaceWith( Xonomy.wyc(searchUrl, function(picklist){ return Xonomy.pickerMenu(picklist, defaultString); }) );
+		});
+	}
+	return false;
+};
+Xonomy.pickerMenu=function(picklist, defaultString){
+	var html="";
+	html+="<div class='menu'>";
+	for(var i=0; i<picklist.length; i++) {
+		var item=picklist[i];
+		if(typeof(item)=="string") item={value: item, caption: ""};
+		html+="<div class='menuItem focusme techno"+(item.value==defaultString?" current":"")+"' tabindex='1' onclick='Xonomy.answer(\""+Xonomy.xmlEscape(item.value)+"\")'>";
+		var alone=true;
+		html+="<span class='punc'>\"</span>";
+		if(item.displayValue) {
+			html+=Xonomy.textByLang(item.displayValue);
+			alone=false;
+		} else {
+			html+=Xonomy.xmlEscape(item.value);
+			if(item.value) alone=false;
+		}
+		html+="<span class='punc'>\"</span>";
+		if(item.caption!="") html+=" <span class='explainer "+(alone?"alone":"")+"'>"+Xonomy.xmlEscape(Xonomy.textByLang(item.caption))+"</span>";
+		html+="</div>";
+	}
+	html+="</div>";
+	return html;
+};
+
+Xonomy.wycLastID=0;
+Xonomy.wycCache={};
+Xonomy.wyc=function(url, callback){ //a "when-you-can" function for delayed rendering: gets json from url, passes it to callback, and delayed-returns html-as-string from callback
+	Xonomy.wycLastID++;
+	var wycID="xonomy_wyc_"+Xonomy.wycLastID;
+	if(Xonomy.wycCache[url]) return callback(Xonomy.wycCache[url]);
+	$.ajax({url: url, dataType: "json", method: "POST"}).done(function(data){
+			$("#"+wycID).replaceWith(callback(data));
+			Xonomy.wycCache[url]=data;
+	});
+	return "<span class='wyc' id='"+wycID+"'></span>";
+};
+
+Xonomy.toggleSubmenu=function(menuItem){
+	var $menuItem=$(menuItem);
+	if($menuItem.hasClass("expanded")){ $menuItem.find(".submenu").first().slideUp("fast", function(){$menuItem.removeClass("expanded");}); }
+	else { $menuItem.find(".submenu").first().slideDown("fast", function(){$menuItem.addClass("expanded");}); };
+}
+Xonomy.internalMenu=function(htmlID, items, harvest, getter, indices) {
+	indices = indices || [];
+	var fragments = items.map(function (item, i) {
+		Xonomy.verifyDocSpecMenuItem(item);
+		var jsMe=harvest(document.getElementById(htmlID));
+		var includeIt=!item.hideIf(jsMe);
+		var html="";
+		if(includeIt) {
+			indices.push(i);
+			var icon=""; if(item.icon) icon="<span class='icon'><img src='"+item.icon+"'/></span> ";
+			var key=""; if(item.keyTrigger && item.keyCaption) key="<span class='keyCaption'>"+Xonomy.textByLang(item.keyCaption)+"</span>";
+			if (item.menu) {
+				var internalHtml=Xonomy.internalMenu(htmlID, item.menu, harvest, getter, indices);
+				if(internalHtml!="<div class='submenu'></div>") {
+					html+="<div class='menuItem"+(item.expanded(jsMe)?" expanded":"")+"'>";
+					html+="<div class='menuLabel focusme' tabindex='0' onkeydown='if(Xonomy.keyNav && [37, 39].indexOf(event.which)>-1) Xonomy.toggleSubmenu(this.parentNode)' onclick='Xonomy.toggleSubmenu(this.parentNode)'>"+icon+Xonomy.formatCaption(Xonomy.textByLang(item.caption(jsMe)))+"</div>";
+					html+=internalHtml;
+					html+="</div>";
+				}
+			} else {
+				html+="<div class='menuItem focusme' tabindex='0' onclick='Xonomy.callMenuFunction("+getter(indices)+", \""+htmlID+"\")'>";
+				html+=key+icon+Xonomy.formatCaption(Xonomy.textByLang(item.caption(jsMe)));
+				html+="</div>";
+			}
+			indices.pop();
+		}
+		return html;
+	});
+	var cls = !indices.length ? 'menu' : 'submenu';
+	return fragments.length
+		? "<div class='"+cls+"'>"+fragments.join("")+"</div>"
+		: "";
+};
+Xonomy.attributeMenu=function(htmlID) {
+	var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name
+	var elName=$("#"+htmlID).closest(".element").attr("data-name"); //obtain element's name
+	Xonomy.verifyDocSpecAttribute(elName, name);
+	var spec=Xonomy.docSpec.elements[elName].attributes[name];
+	function getter(indices) {
+		return 'Xonomy.docSpec.elements["'+elName+'"].attributes["'+name+'"].menu['+indices.join('].menu[')+']';
+	}
+	return Xonomy.internalMenu(htmlID, spec.menu, Xonomy.harvestAttribute, getter);
+};
+Xonomy.elementMenu=function(htmlID) {
+	var elName=$("#"+htmlID).attr("data-name"); //obtain element's name
+	var spec=Xonomy.docSpec.elements[elName];
+	function getter(indices) {
+		return 'Xonomy.docSpec.elements["'+elName+'"].menu['+indices.join('].menu[')+']';
+	}
+	return Xonomy.internalMenu(htmlID, spec.menu, Xonomy.harvestElement, getter);
+};
+Xonomy.inlineMenu=function(htmlID) {
+	var elName=$("#"+htmlID).attr("data-name"); //obtain element's name
+	var spec=Xonomy.docSpec.elements[elName];
+	function getter(indices) {
+		return 'Xonomy.docSpec.elements["'+elName+'"].inlineMenu['+indices.join('].menu[')+']';
+	}
+	return Xonomy.internalMenu(htmlID, spec.inlineMenu, Xonomy.harvestElement, getter);
+};
+Xonomy.callMenuFunction=function(menuItem, htmlID) {
+	menuItem.action(htmlID, menuItem.actionParameter);
+};
+Xonomy.formatCaption=function(caption) {
+	caption=caption.replace(/\<(\/?)([^\>\/]+)(\/?)\>/g, "<span class='techno'><span class='punc'>&lt;$1</span><span class='elName'>$2</span><span class='punc'>$3&gt;</span></span>");
+	caption=caption.replace(/\@"([^\"]+)"/g, "<span class='techno'><span class='punc'>\"</span><span class='atValue'>$1</span><span class='punc'>\"</span></span>");
+	caption=caption.replace(/\@([^ =]+)=""/g, "<span class='techno'><span class='atName'>$1</span><span class='punc'>=\"</span><span class='punc'>\"</span></span>");
+	caption=caption.replace(/\@([^ =]+)="([^\"]+)"/g, "<span class='techno'><span class='atName'>$1</span><span class='punc'>=\"</span><span class='atValue'>$2</span><span class='punc'>\"</span></span>");
+	caption=caption.replace(/\@([^ =]+)/g, "<span class='techno'><span class='atName'>$1</span></span>");
+	return caption;
+};
+
+Xonomy.deleteAttribute=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var obj=document.getElementById(htmlID);
+	var parentID=obj.parentNode.parentNode.parentNode.id;
+	obj.parentNode.removeChild(obj);
+	Xonomy.changed();
+	window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName"); }, 100);
+};
+Xonomy.deleteElement=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var obj=document.getElementById(htmlID);
+	var parentID=obj.parentNode.parentNode.id;
+	$(obj).fadeOut(function(){
+		var parentNode=obj.parentNode;
+		parentNode.removeChild(obj);
+		Xonomy.changed();
+		if($(parentNode).closest(".layby").length==0) {
+			window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName");  }, 100);
+		}
+	});
+};
+Xonomy.newAttribute=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var $element=$("#"+htmlID);
+	var html=Xonomy.renderAttribute({type: "attribute", name: parameter.name, value: parameter.value}, $element.data("name"));
+	$("#"+htmlID+" > .tag.opening > .attributes").append(html);
+	Xonomy.changed();
+	//if the attribute we have just added is shy, force rollout:
+	if($("#"+htmlID+" > .tag.opening > .attributes").children("[data-name='"+parameter.name+"'].shy").toArray().length>0) {
+		if( !$("#"+htmlID).children(".tag.opening").children(".rollouter").hasClass("rolledout") ) {
+			$("#"+htmlID).children(".tag.opening").children(".rollouter").addClass("rolledout");
+			$("#"+htmlID).children(".tag.opening").children(".attributes").addClass("rolledout").hide().slideDown("fast");
+		}
+	}
+	if(parameter.value=="") Xonomy.click($(html).prop("id"), "attributeValue"); else Xonomy.focus($(html).prop("id"), "attributeValue");
+};
+Xonomy.newElementChild=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
+	var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement));
+	var $html=$(html).hide();
+	$("#"+htmlID+" > .children").append($html);
+	Xonomy.plusminus(htmlID, true);
+	Xonomy.elementReorder($html.attr("id"));
+	Xonomy.changed();
+	$html.fadeIn();
+	window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
+};
+Xonomy.elementReorder=function(htmlID){
+	var that=document.getElementById(htmlID);
+	var elSpec=Xonomy.docSpec.elements[that.getAttribute("data-name")];
+	if(elSpec.mustBeBefore) { //is it after an element it cannot be after? then move it up until it's not!
+		var $this=$(that);
+		var jsElement=Xonomy.harvestElement(that);
+		var mustBeBefore=elSpec.mustBeBefore(jsElement);
+		var ok; do {
+			ok=true;
+			for(var ii=0; ii<mustBeBefore.length; ii++) {
+				if( $this.prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
+					$this.prev().before($this);
+					ok=false;
+				}
+			}
+		} while(!ok)
+	}
+	if(elSpec.mustBeAfter) { //is it before an element it cannot be before? then move it down until it's not!
+		var $this=$(that);
+		var jsElement=Xonomy.harvestElement(that);
+		var mustBeAfter=elSpec.mustBeAfter(jsElement);
+		var ok; do {
+			ok=true;
+			for(var ii=0; ii<mustBeAfter.length; ii++) {
+				if( $this.nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
+					$this.next().after($this);
+					ok=false;
+				}
+			}
+		} while(!ok)
+	}
+};
+Xonomy.newElementBefore=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
+	var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement.parent()));
+	var $html=$(html).hide();
+	$("#"+htmlID).before($html);
+	Xonomy.elementReorder($html.prop("id"));
+	Xonomy.changed();
+	$html.fadeIn();
+	window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
+};
+Xonomy.newElementAfter=function(htmlID, parameter) {
+	Xonomy.clickoff();
+	var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
+	var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement.parent()));
+	var $html=$(html).hide();
+	$("#"+htmlID).after($html);
+	Xonomy.elementReorder($html.prop("id"));
+	Xonomy.changed();
+	$html.fadeIn();
+	window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
+};
+Xonomy.replace=function(htmlID, jsNode) {
+	var what=Xonomy.currentFocus;
+	Xonomy.clickoff();
+	var html="";
+	if(jsNode.type=="element") html=Xonomy.renderElement(jsNode);
+	if(jsNode.type=="attribute") html=Xonomy.renderAttribute(jsNode);
+	if(jsNode.type=="text") html=Xonomy.renderText(jsNode);
+	$("#"+htmlID).replaceWith(html);
+	Xonomy.changed();
+	window.setTimeout(function(){ Xonomy.setFocus($(html).prop("id"), what); }, 100);
+};
+Xonomy.editRaw=function(htmlID, parameter) {
+	var div=document.getElementById(htmlID);
+	var jsElement=Xonomy.harvestElement(div);
+	if(parameter.fromJs) var txt=parameter.fromJs( jsElement );
+	else if(parameter.fromXml) var txt=parameter.fromXml( Xonomy.js2xml(jsElement) );
+	else var txt=Xonomy.js2xml(jsElement);
+	document.body.appendChild(Xonomy.makeBubble(Xonomy.askLongString(txt))); //create bubble
+	Xonomy.showBubble($(div)); //anchor bubble to element
+	Xonomy.answer=function(val) {
+		var jsNewElement;
+		if(parameter.toJs) jsNewElement=parameter.toJs(val, jsElement);
+		else if(parameter.toXml) jsNewElement=Xonomy.xml2js(parameter.toXml(val, jsElement), jsElement.parent());
+		else jsNewElement=Xonomy.xml2js(val, jsElement.parent());
+
+		var obj=document.getElementById(htmlID);
+		var html=Xonomy.renderElement(jsNewElement);
+		$(obj).replaceWith(html);
+		Xonomy.clickoff();
+		Xonomy.changed();
+		window.setTimeout(function(){ Xonomy.setFocus($(html).prop("id"), "openingTagName"); }, 100);
+	};
+};
+Xonomy.duplicateElement=function(htmlID) {
+	Xonomy.clickoff();
+	var html=document.getElementById(htmlID).outerHTML.replace(/ id=['"]/g, function(x){return x+"d_"});
+	var $html=$(html).hide();
+	$("#"+htmlID).after($html);
+	Xonomy.changed();
+	$html.fadeIn();
+	window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
+};
+Xonomy.moveElementUp=function(htmlID){
+	Xonomy.clickoff();
+	var $me=$("#"+htmlID);
+	if($me.closest(".layby > .content").length==0) {
+		Xonomy.insertDropTargets(htmlID);
+		var $droppers=$(".xonomy .elementDropper").add($me);
+		var i=$droppers.index($me[0])-1;
+		if(i>=0) {
+			$($droppers[i]).replaceWith($me);
+			Xonomy.changed();
+			$me.hide().fadeIn();
+		}
+		Xonomy.dragend();
+	}
+	window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
+};
+Xonomy.moveElementDown=function(htmlID){
+	Xonomy.clickoff();
+	var $me=$("#"+htmlID);
+	if($me.closest(".layby > .content").length==0) {
+		Xonomy.insertDropTargets(htmlID);
+		var $droppers=$(".xonomy .elementDropper").add($me);
+		var i=$droppers.index($me[0])+1;
+		if(i<$droppers.length) {
+			$($droppers[i]).replaceWith($me);
+			Xonomy.changed();
+			$me.hide().fadeIn();
+		}
+		Xonomy.dragend();
+	}
+	window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
+};
+Xonomy.canMoveElementUp=function(htmlID){
+	var ret=false;
+	var $me=$("#"+htmlID);
+	if($me.closest(".layby > .content").length==0) {
+		Xonomy.insertDropTargets(htmlID);
+		var $droppers=$(".xonomy .elementDropper").add($me);
+		var i=$droppers.index($me[0])-1;
+		if(i>=0) ret=true;
+		Xonomy.dragend();
+	}
+	return ret;
+};
+Xonomy.canMoveElementDown=function(htmlID){
+	var ret=false;
+	var $me=$("#"+htmlID);
+	if($me.closest(".layby > .content").length==0) {
+		Xonomy.insertDropTargets(htmlID);
+		var $droppers=$(".xonomy .elementDropper").add($me);
+		var i=$droppers.index($me[0])+1;
+		if(i<$droppers.length) ret=true;
+		Xonomy.dragend();
+	}
+	return ret;
+};
+Xonomy.mergeWithPrevious=function(htmlID, parameter){
+	var domDead=document.getElementById(htmlID);
+	var elDead=Xonomy.harvestElement(domDead);
+	var elLive=elDead.getPrecedingSibling();
+	Xonomy.mergeElements(elDead, elLive);
+};
+Xonomy.mergeWithNext=function(htmlID, parameter){
+	var domDead=document.getElementById(htmlID);
+	var elDead=Xonomy.harvestElement(domDead);
+	var elLive=elDead.getFollowingSibling();
+	Xonomy.mergeElements(elDead, elLive);
+};
+Xonomy.mergeElements=function(elDead, elLive){
+	Xonomy.clickoff();
+	var domDead=document.getElementById(elDead.htmlID);
+	if(elLive && elLive.type=="element") {
+		for(var i=0; i<elDead.attributes.length; i++){ //merge attributes
+			var atDead=elDead.attributes[i];
+			if(!elLive.hasAttribute(atDead.name) || elLive.getAttributeValue(atDead.name)==""){
+				elLive.setAttribute(atDead.name, atDead.value);
+				if(elLive.hasAttribute(atDead.name)) $("#"+elLive.getAttribute(atDead.name).htmlID).remove();
+				$("#"+elLive.htmlID).find(".attributes").first().append($("#"+elDead.attributes[i].htmlID));
+			}
+		}
+		var specDead=Xonomy.docSpec.elements[elDead.name];
+		var specLive=Xonomy.docSpec.elements[elLive.name];
+		if(specDead.hasText(elDead) || specLive.hasText(elLive)){ //if either element is meant to have text, concatenate their children
+			if(elLive.getText()!="" && elDead.getText()!="") {
+				elLive.addText(" ");
+				$("#"+elLive.htmlID).find(".children").first().append(Xonomy.renderText({type: "text", value: " "}));
+			}
+			for(var i=0; i<elDead.children.length; i++) {
+				elLive.children.push(elDead.children[i]);
+				$("#"+elLive.htmlID).find(".children").first().append($("#"+elDead.children[i].htmlID));
+			}
+		} else { //if no text, merge their children one by one
+			for(var i=0; i<elDead.children.length; i++){
+				var xmlDeadChild=Xonomy.js2xml(elDead.children[i]);
+				var has=false;
+				for(y=0; y<elLive.children.length; y++){
+					var xmlLiveChild=Xonomy.js2xml(elLive.children[y]);
+					if(xmlDeadChild==xmlLiveChild){ has=true; break; }
+				}
+				if(!has) {
+					elLive.children.push(elDead.children[i]);
+					$("#"+elLive.htmlID).find(".children").first().append($("#"+elDead.children[i].htmlID));
+					Xonomy.elementReorder(elDead.children[i].htmlID);
+				}
+			}
+		}
+		domDead.parentNode.removeChild(domDead);
+		Xonomy.changed();
+		window.setTimeout(function(){ Xonomy.setFocus(elLive.htmlID, "openingTagName"); }, 100);
+	} else {
+		window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
+	}
+};
+
+Xonomy.insertDropTargets=function(htmlID){
+	var $element=$("#"+htmlID);
+	$element.addClass("dragging");
+	var elementName=$element.attr("data-name");
+	var elSpec=Xonomy.docSpec.elements[elementName];
+	$(".xonomy .children:visible").append("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
+	$(".xonomy .children:visible > .element").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
+	$(".xonomy .children:visible > .text").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
+	$(".xonomy .dragging .children:visible > .elementDropper").remove(); //remove drop targets fom inside the element being dragged
+	$(".xonomy .dragging").prev(".elementDropper").remove(); //remove drop targets from immediately before the element being dragged
+	$(".xonomy .dragging").next(".elementDropper").remove(); //remove drop targets from immediately after the element being dragged
+	$(".xonomy .children:visible > .element.readonly .elementDropper").remove(); //remove drop targets from inside read-only elements
+
+	var harvestCache={};
+	var harvestElement=function(div){
+		var htmlID=$(div).prop("id");
+		if(!harvestCache[htmlID]) harvestCache[htmlID]=Xonomy.harvestElement(div);
+		return harvestCache[htmlID];
+	};
+
+	if(elSpec.localDropOnly(harvestElement($element.toArray()[0]))) {
+		if(elSpec.canDropTo) { //remove the drop target from elements that are not the dragged element's parent
+			var droppers=$(".xonomy .elementDropper").toArray();
+			for(var i=0; i<droppers.length; i++) {
+				var dropper=droppers[i];
+				if(dropper.parentNode!=ev.target.parentNode.parentNode.parentNode) {
+					dropper.parentNode.removeChild(dropper);
+				}
+			}
+		}
+	}
+	if(elSpec.canDropTo) { //remove the drop target from elements it cannot be dropped into
+		var droppers=$(".xonomy .elementDropper").toArray();
+		for(var i=0; i<droppers.length; i++) {
+			var dropper=droppers[i];
+			var parentElementName=$(dropper.parentNode.parentNode).toArray()[0].getAttribute("data-name");
+			if($.inArray(parentElementName, elSpec.canDropTo)<0) {
+				dropper.parentNode.removeChild(dropper);
+			}
+		}
+	}
+	if(elSpec.mustBeBefore) { //remove the drop target from after elements it cannot be after
+		var jsElement=harvestElement($element.toArray()[0]);
+		var droppers=$(".xonomy .elementDropper").toArray();
+		for(var i=0; i<droppers.length; i++) {
+			var dropper=droppers[i];
+			jsElement.internalParent=harvestElement(dropper.parentNode.parentNode); //pretend the element's parent is the dropper's parent
+			var mustBeBefore=elSpec.mustBeBefore(jsElement);
+			for(var ii=0; ii<mustBeBefore.length; ii++) {
+				if( $(dropper).prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
+					dropper.parentNode.removeChild(dropper);
+				}
+			}
+		}
+	}
+	if(elSpec.mustBeAfter) { //remove the drop target from before elements it cannot be before
+		var jsElement=harvestElement($element.toArray()[0]);
+		var droppers=$(".xonomy .elementDropper").toArray();
+		for(var i=0; i<droppers.length; i++) {
+			var dropper=droppers[i];
+			jsElement.internalParent=harvestElement(dropper.parentNode.parentNode); //pretend the element's parent is the dropper's parent
+			var mustBeAfter=elSpec.mustBeAfter(jsElement);
+			for(var ii=0; ii<mustBeAfter.length; ii++) {
+				if( $(dropper).nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
+					dropper.parentNode.removeChild(dropper);
+				}
+			}
+		}
+	}
+};
+Xonomy.draggingID=null; //what are we dragging?
+Xonomy.drag=function(ev) { //called when dragging starts
+	// Wrapping all the code into a timeout handler is a workaround for a Chrome browser bug
+	// (if the DOM is manipulated in the 'dragStart' event then 'dragEnd' event is sometimes fired immediately)
+	//
+	// for more details @see:
+	//   http://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
+	ev.dataTransfer.effectAllowed="move"; //only allow moving (and not eg. copying]
+	var htmlID=ev.target.parentNode.parentNode.id;
+	ev.dataTransfer.setData("text", htmlID);
+	setTimeout(function() {
+		Xonomy.clickoff();
+		Xonomy.insertDropTargets(htmlID);
+		Xonomy.draggingID=htmlID;
+		Xonomy.refresh();
+	}, 10);
+};
+Xonomy.dragOver=function(ev) {
+	ev.preventDefault();
+	ev.dataTransfer.dropEffect="move"; //only allow moving (and not eg. copying]
+	if($(ev.currentTarget).hasClass("layby")){
+		$(ev.currentTarget).addClass("activeDropper");
+	} else {
+		$(ev.target.parentNode).addClass("activeDropper");
+	}
+};
+Xonomy.dragOut=function(ev) {
+	ev.preventDefault();
+	if($(ev.currentTarget).hasClass("layby")){
+		$(ev.currentTarget).removeClass("activeDropper");
+	} else {
+		$(".xonomy .activeDropper").removeClass("activeDropper");
+	}
+};
+Xonomy.drop=function(ev) {
+	ev.preventDefault();
+	var node=document.getElementById(Xonomy.draggingID); //the thing we are moving
+	if($(ev.currentTarget).hasClass("layby")) {
+		$(node).hide();
+		$(".xonomy .layby > .content").append(node);
+		$(node).fadeIn(function(){ Xonomy.changed(); });
+	} else {
+		$(node).hide();
+		$(ev.target.parentNode).replaceWith(node);
+		$(node).fadeIn(function(){ Xonomy.changed(); });
+	}
+	Xonomy.openCloseLayby();
+	Xonomy.recomputeLayby();
+};
+Xonomy.dragend=function(ev) {
+	$(".xonomy .attributeDropper").remove();
+	$(".xonomy .elementDropper").remove();
+	$(".xonomy .dragging").removeClass("dragging");
+	Xonomy.refresh();
+	$(".xonomy .layby").removeClass("activeDropper");
+};
+
+Xonomy.openCloseLayby=function(){ //open the layby if it's full, close it if it's empty
+	if($(".xonomy .layby > .content > *").length>0){
+		$(".xonomy .layby").removeClass("closed").addClass("open");
+	} else {
+		$(".xonomy .layby").removeClass("open").addClass("closed");
+	}
+};
+Xonomy.openLayby=function(){
+	$(".xonomy .layby").removeClass("closed").addClass("open");
+};
+Xonomy.closeLayby=function(){
+	window.setTimeout(function(){
+		$(".xonomy .layby").removeClass("open").addClass("closed");
+	}, 10);
+};
+Xonomy.emptyLayby=function(){
+	$(".xonomy .layby .content").html("");
+	$(".xonomy .layby").removeClass("nonempty").addClass("empty");
+};
+Xonomy.recomputeLayby=function(){
+	if($(".xonomy .layby > .content > *").length>0){
+		$(".xonomy .layby").removeClass("empty").addClass("nonempty");
+	} else {
+		$(".xonomy .layby").removeClass("nonempty").addClass("empty");
+	}
+}
+
+Xonomy.changed=function(jsElement) { //called when the document changes
+	Xonomy.harvestCache={};
+	Xonomy.refresh();
+	Xonomy.validate();
+	Xonomy.docSpec.onchange(jsElement); //report that the document has changed
+};
+Xonomy.validate=function() {
+	var js=Xonomy.harvestElement($(".xonomy .element").toArray()[0], null);
+	$(".xonomy .invalid").removeClass("invalid");
+	Xonomy.warnings=[];
+	Xonomy.docSpec.validate(js); //validate the document
+	for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) {
+		var warning=Xonomy.warnings[iWarning];
+		$("#"+warning.htmlID).addClass("invalid");
+	}
+};
+Xonomy.warnings=[]; //array of {htmlID: "", text: ""}
+
+Xonomy.textByLang=function(str) {
+	//str = eg. "en: Delete | de: Löschen | fr: Supprimer"
+	if(!str) str="";
+	var ret=str;
+	var segs=str.split("|");
+	for(var i=0; i<segs.length; i++) {
+		var seg=$.trim(segs[i]);
+		if(seg.indexOf(Xonomy.lang+":")==0) {
+			ret=seg.substring((Xonomy.lang+":").length, ret.length);
+		}
+	}
+	ret=$.trim(ret);
+	return ret;
+};
+
+Xonomy.currentHtmlId=null;
+Xonomy.currentFocus=null;
+Xonomy.keyNav=false;
+Xonomy.startKeyNav=function(keyboardEventCatcher, scrollableContainer){
+	Xonomy.keyNav=true;
+	var $keyboardEventCatcher=$(keyboardEventCatcher); if(!keyboardEventCatcher) $keyboardEventCatcher=$(".xonomy");
+	$scrollableContainer=$(scrollableContainer); if(!scrollableContainer) $scrollableContainer=$keyboardEventCatcher;
+	$keyboardEventCatcher.attr("tabindex", "0");
+	$keyboardEventCatcher.on("keydown", Xonomy.key);
+	$(document).on("keydown", function(e) { if([32, 37, 38, 39, 40].indexOf(e.keyCode)>-1 && $("input:focus, select:focus, textarea:focus").length==0) e.preventDefault(); }); //prevent default browser scrolling on arrow keys
+	Xonomy.keyboardEventCatcher=$keyboardEventCatcher;
+	Xonomy.scrollableContainer=$scrollableContainer;
+};
+Xonomy.setFocus=function(htmlID, what){
+	if(Xonomy.keyNav) {
+		$(".xonomy .current").removeClass("current");
+		$(".xonomy .focused").removeClass("focused");
+		if(what=="attributeValue") $("#"+htmlID+" > .valueContainer").addClass("current").addClass("focused");
+		else $("#"+htmlID).addClass("current").addClass("focused");
+		Xonomy.currentHtmlId=htmlID;
+		Xonomy.currentFocus=what;
+		if(Xonomy.currentFocus=="openingTagName") $("#"+htmlID+" > .tag.opening").first().addClass("focused");
+		if(Xonomy.currentFocus=="closingTagName") $("#"+htmlID+" > .tag.closing").last().addClass("focused");
+		if(Xonomy.currentFocus=="childrenCollapsed") $("#"+htmlID+" > .childrenCollapsed").last().addClass("focused");
+		if(Xonomy.currentFocus=="rollouter") $("#"+htmlID+" > .tag.opening > .rollouter").last().addClass("focused");
+	}
+};
+Xonomy.key=function(event){
+	if(!Xonomy.notKeyUp) {
+		if(!event.shiftKey && !$("#xonomyBubble").length>0 ) {
+			if(event.which==27) { //escape key
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				Xonomy.destroyBubble();
+			} else if(event.which==13){ //enter key
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				if(Xonomy.currentFocus=="childrenCollapsed") Xonomy.plusminus(Xonomy.currentHtmlId, true);
+				if(Xonomy.currentFocus=="char") {
+					Xonomy.charClick($("#"+Xonomy.currentHtmlId)[0]);
+				}
+				else {
+					Xonomy.click(Xonomy.currentHtmlId, Xonomy.currentFocus);
+					Xonomy.clickoff();
+				}
+			} else if((event.ctrlKey || event.metaKey) && event.which==40) { //down key with Ctrl or Cmd (Mac OS)
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				Xonomy.scrollableContainer.scrollTop( Xonomy.scrollableContainer.scrollTop()+60 );
+			} else if((event.ctrlKey || event.metaKey) && event.which==38) { //up key with Ctrl or Cmd (Mac OS)
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				Xonomy.scrollableContainer.scrollTop( Xonomy.scrollableContainer.scrollTop()-60 );
+			} else if((event.ctrlKey || event.metaKey) && [37, 39].indexOf(event.which)>-1) { //arrow keys with Ctrl or Cmd (Mac OS)
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				var $el=$("#"+Xonomy.currentHtmlId);
+				if($el.hasClass("element") && !$el.hasClass("uncollapsible")){
+					if(event.which==39 && $el.hasClass("collapsed")) { //expand it!
+						Xonomy.plusminus(Xonomy.currentHtmlId);
+					}
+					if(event.which==37 && !$el.hasClass("collapsed")) { //collapse it!
+						Xonomy.plusminus(Xonomy.currentHtmlId);
+					}
+				}
+			} else if([37, 38, 39, 40].indexOf(event.which)>-1 && !event.altKey) { //arrow keys
+				event.preventDefault();
+				event.stopImmediatePropagation();
+				if(!Xonomy.currentHtmlId) { //nothing is current yet
+					Xonomy.setFocus($(".xonomy .element").first().prop("id"), "openingTagName");
+				} else if($(".xonomy .focused").length==0) { //something is current but nothing is focused yet
+					Xonomy.setFocus(Xonomy.currentHtmlId, Xonomy.currentFocus);
+				} else { //something is current, do arrow action
+					if(event.which==40) Xonomy.goDown(); //down key
+					if(event.which==38) Xonomy.goUp(); //up key
+					if(event.which==39) Xonomy.goRight(); //right key
+					if(event.which==37) Xonomy.goLeft(); //left key
+				}
+			}
+		} else if(!$("#xonomyBubble").length>0) {
+			Xonomy.keyboardMenu(event);
+		}
+	}
+	Xonomy.notKeyUp=false;
+};
+Xonomy.keyboardMenu=function(event){
+	var $obj=$("#"+Xonomy.currentHtmlId);
+	var jsMe=null;
+	var menu=null;
+	if($obj.hasClass("element")){
+		jsMe=Xonomy.harvestElement($obj[0]);
+		var elName=$obj.attr("data-name");
+		menu=Xonomy.docSpec.elements[elName].menu;
+	} else if($obj.hasClass("attribute")) {
+		jsMe=Xonomy.harvestAttribute($obj[0]);
+		var atName=$obj.attr("data-name");
+		var elName=$obj.closest(".element").attr("data-name");
+		menu=Xonomy.docSpec.elements[elName].attributes[atName].menu;
+	}
+	if(menu){
+		var findMenuItem=function(menu){
+			var ret=null;
+			for(var i=0; i<menu.length; i++){
+				if(menu[i].menu) ret=findMenuItem(menu[i].menu);
+				else if(menu[i].keyTrigger && !menu[i].hideIf(jsMe) && menu[i].keyTrigger(event)) ret=menu[i];
+				if(ret) break;
+			}
+			return ret;
+		};
+		var menuItem=findMenuItem(menu);
+		if(menuItem) {
+			Xonomy.callMenuFunction(menuItem, Xonomy.currentHtmlId);
+			Xonomy.clickoff();
+			return true;
+		}
+	}
+	return false;
+},
+
+Xonomy.goDown=function(){
+	if(Xonomy.currentFocus!="openingTagName" && Xonomy.currentFocus!="closingTagName" && Xonomy.currentFocus!="text" && Xonomy.currentFocus!="char") {
+		Xonomy.goRight();
+	} else {
+		var $el=$("#"+Xonomy.currentHtmlId);
+		var $me=$el;
+		if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
+		if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
+
+		var $candidates=$(".xonomy .focusable:visible").not(".attributeName").not(".attributeValue").not(".childrenCollapsed").not(".rollouter");
+		$candidates=$candidates.not(".char").add($el);
+		if(Xonomy.currentFocus=="openingTagName" && $el.hasClass("oneliner")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .tag.closing").not("#"+Xonomy.currentHtmlId+" .children *");
+		if(Xonomy.currentFocus=="openingTagName" && $el.hasClass("oneliner")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .textnode");
+		if($el.hasClass("collapsed")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .tag.closing");
+		if($el.hasClass("textnode") && $(".xonomy").hasClass("nerd")) var $candidates=$el.closest(".element").find(".tag.closing").last();
+		if($el.hasClass("textnode") && $(".xonomy").hasClass("laic")) var $candidates=$el.closest(".element").next().find(".focusable:visible").first();
+
+		var $next=$candidates.eq( $candidates.index($me[0])+1 );
+		if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
+		if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
+		if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
+	}
+};
+Xonomy.goUp=function(){
+	if(Xonomy.currentFocus!="openingTagName" && Xonomy.currentFocus!="closingTagName" && Xonomy.currentFocus!="char" && Xonomy.currentFocus!="text") {
+		Xonomy.goLeft();
+	} else {
+		var $el=$("#"+Xonomy.currentHtmlId);
+		var $me=$el;
+		if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
+		if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
+
+		var $candidates=$(".xonomy .focusable:visible").not(".attributeName").not(".attributeValue").not(".childrenCollapsed").not(".rollouter");
+		$candidates=$candidates.not(".element .oneliner .tag.closing");
+		$candidates=$candidates.not(".element .oneliner .textnode");
+		$candidates=$candidates.not(".element .collapsed .tag.closing");
+		$candidates=$candidates.not(".char");
+		if($el.hasClass("char")) var $candidates=$el.closest(".textnode").first().add($el);
+		if($el.hasClass("textnode")) var $candidates=$el.closest(".element").find(".tag.opening").first().add($el);
+		if($me.hasClass("closing") && $el.hasClass("hasText")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .children *:not(:first-child)");
+		if($me.hasClass("opening") && $el.closest(".element").prev().hasClass("hasText")) {
+			var siblingID=$el.closest(".element").prev().prop("id");
+			$candidates=$candidates.not("#"+siblingID+" .children *:not(:first-child)");
+		}
+
+		if($candidates.index($me[0])>0) {
+			var $next=$candidates.eq( $candidates.index($me[0])-1 );
+			if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
+			if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
+			if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
+		}
+	}
+};
+Xonomy.goRight=function(){
+	var $el=$("#"+Xonomy.currentHtmlId);
+	var $me=$el;
+	if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
+	if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
+	if(Xonomy.currentFocus=="attributeName") var $me=$el.find(".attributeName").first();
+	if(Xonomy.currentFocus=="attributeValue") var $me=$el.find(".attributeValue").first();
+	if(Xonomy.currentFocus=="childrenCollapsed") var $me=$el.find(".childrenCollapsed").first();
+	if(Xonomy.currentFocus=="rollouter") var $me=$el.find(".rollouter").first();
+
+	var $candidates=$(".xonomy .focusable:visible");
+	$candidates=$candidates.not(".char").add(".hasInlineMenu > .children > .textnode .char:visible");
+
+	var $next=$candidates.eq( $candidates.index($me[0])+1 );
+	if($next.hasClass("attributeName")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeName");
+	if($next.hasClass("attributeValue")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeValue");
+	if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
+	if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
+	if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
+	if($next.hasClass("childrenCollapsed")) Xonomy.setFocus($next.closest(".element").prop("id"), "childrenCollapsed");
+	if($next.hasClass("rollouter")) Xonomy.setFocus($next.closest(".element").prop("id"), "rollouter");
+	if($next.hasClass("char")) Xonomy.setFocus($next.prop("id"), "char");
+};
+Xonomy.goLeft=function(){
+	var $el=$("#"+Xonomy.currentHtmlId);
+	var $me=$el;
+	if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
+	if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
+	if(Xonomy.currentFocus=="attributeName") var $me=$el.find(".attributeName").first();
+	if(Xonomy.currentFocus=="attributeValue") var $me=$el.find(".attributeValue").first();
+	if(Xonomy.currentFocus=="childrenCollapsed") var $me=$el.find(".childrenCollapsed").first();
+	if(Xonomy.currentFocus=="rollouter") var $me=$el.find(".rollouter").first();
+
+	var $candidates=$(".xonomy .focusable:visible");
+	$candidates=$candidates.not(".char").add(".hasInlineMenu > .children > .textnode .char:visible");
+
+	var $next=$candidates.eq( $candidates.index($me[0])-1 );
+	if($next.hasClass("attributeName")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeName");
+	if($next.hasClass("attributeValue")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeValue");
+	if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
+	if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
+	if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
+	if($next.hasClass("childrenCollapsed")) Xonomy.setFocus($next.closest(".element").prop("id"), "childrenCollapsed");
+	if($next.hasClass("rollouter")) Xonomy.setFocus($next.closest(".element").prop("id"), "rollouter");
+	if($next.hasClass("char")) Xonomy.setFocus($next.prop("id"), "char");
+};

+ 3 - 0
routes/repo/editor.go

@@ -80,6 +80,7 @@ func editFile(c *context.Context, isNewFile bool) {
 		c.Data["IsJSON"] = markup.IsJSON(blob.Name())
 		c.Data["IsYAML"] = markup.IsYAML(blob.Name())
 
+
 		buf := make([]byte, 1024)
 		n, _ := dataRc.Read(buf)
 		buf = buf[:n]
@@ -90,6 +91,8 @@ func editFile(c *context.Context, isNewFile bool) {
 			return
 		}
 
+		c.Data["IsOdML"] = tool.IsOdmlFile(buf)
+
 		d, _ := ioutil.ReadAll(dataRc)
 		buf = append(buf, d...)
 		if err, content := template.ToUTF8WithErr(buf); err != nil {

+ 3 - 0
templates/base/head.tmpl

@@ -63,6 +63,9 @@
 		{{end}}
 
 		{{if .IsOdML}}
+		<script type="text/javascript" src="{{AppSubURL}}/plugins/xonomy/xonomy.js"></script>
+		<link type="text/css" rel="stylesheet" href="{{AppSubURL}}/plugins/xonomy/xonomy.css"/>
+
 		<script src="{{AppSubURL}}/js/libs/jstree.min.js"></script>
 		<link rel="stylesheet" href="{{AppSubURL}}/css/jstree/jstree.css">
 		{{end}}

+ 57 - 0
templates/repo/editor/edit.tmpl

@@ -35,6 +35,9 @@
 					{{if .IsYAML}}
 					<a class="item" data-tab="yamleditor" data-tooltip="Using the graphical YAML editor will change the file layout and remove comments!"><i class="octicon octicon-file"></i>Graphical Editor</a>
 					{{end}}
+					{{if .IsOdML}}
+					<a class="item" data-tab="odmleditor"><i class="octicon octicon-file"></i>Graphical Editor</a>
+					{{end}}
 					{{if not .IsNewFile}}
 					<a class="item" id="preview-tab" data-tab="preview" data-url="{{AppSubURL}}/api/v1/markdown" data-root-context="{{.BranchLink}}/" data-context="{{.BranchLink}}/{{.ParentTreePath}}" data-preview-file-modes="{{.PreviewableFileModes}}"><i class="octicon octicon-eye"></i> {{.i18n.Tr "repo.release.preview"}}</a>
 					<a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName}}/{{.TreePath}}"><i class="octicon octicon-diff"></i> {{.i18n.Tr "repo.editor.preview_changes"}}</a>
@@ -102,6 +105,60 @@
 					</script>
 				</div>
 				{{end}}
+
+				{{if .IsOdML}}
+				<div class="ui bottom attached tab segment" data-tab="odmleditor">
+					<div id="xonomy_edit"></div>
+				</div>
+				<script type="text/javascript">
+					var docSpec={
+						elements: {
+							"value": {
+								hasText: true,
+								collapsible:false,
+								menu: [{
+									caption: "Delete this <value>",
+									action: Xonomy.deleteElement
+								}, {
+									caption: "Add <unit>",
+									action: Xonomy.newElementChild,
+									actionParameter: "<unit>...</unit>"
+								}]
+							},
+							"unit": {
+								hasText: true,
+								collapsible:false
+							},
+							"name": {
+								hasText: true,
+								mustBeBefore:["value"],
+								collapsible:false
+							},
+							"property": {
+								oneliner: true,
+								collapsible:false,
+								hasText: false,
+								menu: [{
+									caption: "Delete this <property>",
+									action: Xonomy.deleteElement
+								}, {
+									caption: "New <property> before this",
+									action: Xonomy.newElementBefore,
+									actionParameter: "<property><name>...</name><value>...</value></property>"
+								}, {
+									caption: "New <property> after this",
+									action: Xonomy.newElementAfter,
+									actionParameter: "<property><name>...</name><value>...</value></property>"
+								}]
+							}
+						},
+					allowModeSwitching:true
+					}
+						var xml={{.FileContent}};
+						var editor=document.getElementById("xonomy_edit");
+						Xonomy.render(xml, editor, docSpec);
+				</script>
+				{{end}}
 			</div>
 			{{template "repo/editor/commit_form" .}}
 		</form>