私は良いエレガントな解決策を見つけました:
textselector.js (マークの選択 - ブックマーを CKEDITOR に挿入)
function selectText(irtId, startIdSelectionId, endIdSelectionId) {
var editor = CKEDITOR.instances[irtId];
if (editor.getSelection().getRanges()[0].collapsed) {
document.getElementById(startIdSelectionId).value = "";
document.getElementById(endIdSelectionId).value = "";
} else {
var bookmarks = editor.getSelection().createBookmarks(true);
var startId = bookmarks[0].startNode;
var endId = bookmarks[0].endNode;
document.getElementById(startIdSelectionId).value = startId;
document.getElementById(endIdSelectionId).value = endId;
}
return true;
}
サーバー側でのアクション (競合を特定する - タグの交差、必要に応じて要素を修復する - 2 つの部分に分割してスパンを挿入する):
/**
* prida span na oznacene html osestreni nezadoucich pripadu: prazdny
* select, kolize s jinym spanem
*/
public void spanSelectedHtml() {
// parsovani celeho dokumentu
Document doc = Jsoup.parse(value);
// osetreni chybovych stavu
// - prazdny select
if (startIdSelection.isEmpty() || endIdSelection.isEmpty()) {
return;
}
// nalezeni znacek
Element es = doc.getElementById(startIdSelection);
Element ee = doc.getElementById(endIdSelection);
// - konflikt s jinou znackou
// bude doplneno
// oprava okoli znacek v pripade nutnosti
repairIfNecessary(es, ee);
// vytvoreni span tagu s nalezitymi atributy
Element span = doc.createElement("span");
span.attr("property", clicked.getUrl());
span.attr("class", clicked.getStyleClass());
// nahrazeni prvni znacky span tagem
es.replaceWith(span);
// pripojeni vsech nasledujicich uzlu az do koncove znacky
while (span.nextSibling() != ee) {
span.appendChild(span.nextSibling());
}
// odstraneni koncove znacky
ee.remove();
// aktualizace hodnoty textove komponenty
value = doc.toString();
}
/**
* oprava okoli elementu v pripade nutnosti - pri zjisteni unikatniho rodice
*
* @param e1 prvni element
* @param e2 druhy element
*/
private void repairIfNecessary(Element e1, Element e2) {
while (hasUniqueParent(e1, e2)) { // unikatni rodice e1?
repairElement(e1);
}
while (hasUniqueParent(e2, e1)) { // unikatni rodice e2?
repairElement(e2);
}
}
/**
* oprava okoli elementu: rozdeleni na dve casti a vymazani rodice,
* zachovani atributu
*
* @param e element s okolim na opravu
*/
private void repairElement(Element e) {
// "problemovy rodic", ktereho je treba rozdelit
Element p = e.parent();
// 1. cast - pred znackou
if (e.previousSibling() != null) { // osetreni null
Element n = p.clone().empty(); // vkladany element musi byt formalne stejny
p.prependChild(n); // umisteni elementu na zacatek - jako 1. dite
while (n.nextSibling() != e) { // dokud se nedostanu ke znacce, pridavam uzly
n.appendChild(n.nextSibling());
}
}
// 2. cast - za znackou
if (e.nextSibling() != null) { // osetreni null
Element n = p.clone().empty(); // vkladany element musi byt formalne stejny
p.appendChild(n); // umisteni elementu na konec - jako posledni dite
while (n.previousSibling() != e) { // dokud se nedostanu ke znacce, pridavam uzly
n.prependChild(n.previousSibling());
}
}
p.unwrap(); // vymazani puvodniho "problemoveho rodice"
}
/**
* ma testovaci element rodice, ktereho kontrolni element nema?
*
* @param e testovaci element
* @param c kontrolni element
* @return testovaci element ma unikatniho rodice (takoveho, ktery kontrolni
* element nema)
*/
private boolean hasUniqueParent(Element e, Element c) {
if (e.parents().isEmpty() || c.parents().isEmpty()) { // test na null
return false;
}
for (Element pe : e.parents()) {
if (!c.parents().contains(pe)) {
return true; // unikatni rodic
}
}
return false; // bez unikatniho rodice
}