Я писал инлайн-форматирование для WYSIWYG-редактора следующим образом:
1. Получаем root = range.commonAncestroContainer || textRange.parentElement
2. Находим внутри root все текстовые узлы (nodeType == 3), формируя двумерный массив следующим образом:
<div>
text1
<ul>
<li>text2</li>
</ul>
text3<span>text4</span>text5
</div>
var text = [
[#text1],
[#text2],
[#text3, #text4, #text5]
]
То есть каждый блочный элемент начинает и заканчивает новый подмассив.
3. Проходим в цикле по text и собираем при помощи getComputedStyle/currentStyle все стили, которые собираемся поддерживать, с родителя каждого текстового узла и формируем массив следующего вида:
text = [
{
nodes: [#text1],
CSSStyles: {fontWeight: "bold", …}
},
{
nodes: [#text2],
CSSStyles: {…}
},
{
nodes: [#text3, #text4, #text5],
CSSStyles: {…}
},
]
4. Теперь нужно написать конечный автомат, который сформирует из этих данных куски HTML-кода:
text = [
{
nodes: [#text1],
html: "<b>text1</b>"
},
{
nodes: [text2]
html: "<b>text2</b>"
},
{
nodes: [#text3, #text4, #text5],
html: "<b>text3text4text5<b>"
},
]
5. Преобразуем HTML-код в дом узлы:
function parse(html) {
var node = document.createElement("div"), fragment = document.createDocumentFragment();
node.innerHTML = html;
while (node.firstChild) fragment.appendChild(node.firstChild);
return fragment;
}
text = [
{
oldNodes: [#text1],
newNodes: parse("<b>text1</b>")
},
{
oldNodes: [#text2],
newNodes: parse("<b>text2</b>")
},
{
oldNodes: [#text3, #text4, #text5],
newNodes: parse("<b>text3text4text5</b>")
},
]
6. Для каждого набора текстовых узлов находим блочного родителя и заменяем старые текстовые узлы на новые (не innerHTML!), проходя в цикле по childNodes, пока не встретится блочный элемент.
Теперь подробнее о том, как заменить стили:
используем только одну команду для любого инлайн-форматрирования:
document.execCommand("fontName", false, "fake")
Браузер будет автоматически разрывать инлайн-теги и создавать <font face="fake">. А когда мы будем собирать стили с родителей текстовых узлов, то можем дополнительно проверить наличие этого element.getAttribute("face") == "fake" и изменить объект с информацией о стилях.
Честно говоря, не знаю, был это мой велосипед или совершенно новый алгоритм, потому что одной из задач было в реальном времени показывать красивый HTML-код, что не умел делать ни один из известных мне редакторов, поэтому я сразу стал писать сам.
Код показать не могу, проект коммерческий.