При написании одного парсера передо мной встала задача предоставить пользователю возможность самому выбирать ту часть страницы которую он хотел бы спрарсить. Сделать я это решил на базе WYSWYG редактора JWYSWYG, он очень легкий, кросбраузерный, написан на моем любимом JQuery и легко поддается модификации. Кроме того я встроил в него функцию автоперевода выделенного текста при помощи Google Tranlate Ajax API. От слов к делу. Оказалось что задача не настолько тривиальна как кажется с первого взгляда. Получить со страницы надо было именно кусок выделенного HTML а не тот текст, который собственно попадет в буфер обмена если нажать кнопку копирования.
UPD. Судя по поисковым запросам, много людей ищут не теорию, а готовое решение на jQuery. Дабы не смущать Вас лишней информацией можете сразу пройти на страницу с готовым плагином на JQuery и пример его использования
Первое что мне удалось сделать руководствуясь крупицами нагугленной информации это
var ie = false; if ( window.getSelection ) { var selectedText = window.getSelection(); } else if ( document.getSelection ) { var selectedText = document.getSelection(); } else if ( document.selection ) { ie = true; var selectedText = document.selection.createRange(); } if(!ie){ alert(selectedText)// просто выведет выделенный текст без тегов var theParent = selectedText.getRangeAt(0).cloneContents(); alert(toHTML(theParent)) // собственно выделенный кусок кода }else{ alert(selectedText.text) // текст alert(selectedText.htmlText) // выделенный кусок кода }
Данный код можно протестировать на примере номер 1
Как мы видим код c намеком на кроссбраузерность, проверяются все возможные варианты, остановимся по подробнее на каждой строчке. Метод window.getSelection поддерживается большинством современных браузеров, кроме всеми любимого IE. Метод document.getSelection является устаревшей версией window.getSelection и проверяется для устаревших браузеров. Ну и наконец последняя ветка условия исключительно для браузеров IE.
Собственно объект который получается в результате такого выделения имеет тип selection. Он представляет собой выделенный текст на странице, возможно, охватывающих несколько элементов. Несколько выделений возможны к примеру при нажатии клавиши ctrl.
Чтобы получить содержимое одного из этих выделений применяется метод sel.getRangeAt(index) Где index это порядковый номер выделений на странице. Метод возвращает числовой диапазон - начало и конец выделения. Возвращаемый этим методом объект имеет тип range
Как видно из документации range имеет поля в которых записано начало выделения и поле для записи конца выделения. А также тип имеет несколько методов для получения выделенной части страницы.
cloneContents - Возвращает фрагмент документа(documentFragment) копируя его, т.е. при этом все выделенные узлы дерева DOM остаются на своем месте.
deleteContents - удаляет контент из объекта range. Т.е. выделенный html попросту удалится
extractContents - помещает выделенный дерево узлов DOM в специальный объект documentFragment, при этом из оригинального дерева DOM элементов страницы эти узлы удаляются. Т.е. это эквивалентно последовательному вызову методов cloneContents и deleteContents соответственно
insertNode - вставляет новые элементы в начало выделенной области
surroundContents - оборачивает выделенные узлы в новый элемент(к примеру в <div></div>)
В примере мы использовали cloneContents, но вам ничего не мешает использовать и другие методы.
Немного подробнее остановимся на объекте documentFragment. Это очень интересный объект так как он может содержать в объектной форме множество узлов DOM. Поясню на примере:
<div id="spabox"> <span>a</span> <span>b</span> <span>c</span> </div>
Тут мы видим один узел DIV с тремя вложенными в него узлами SPAN.
Если получить доступ к элементу DIV при помощи
var spanbox = document.getElementById('spanbox') alert(typeof spanbox.childNodes) // вернет Array
то метод возвращающий все дочерние объекты DIV chilNodes будет иметь типа Array, что неприемлемо если мы к примеру хотим групповой операцией вставить их в другое место документа. На помощь приходит documentFragment . На просторах интернета мною даже была найдена функция которая конвертирует HTML текст в DOM(как оказалось довольно нетривиальная задача в js программировании)
function toDOM(HTMLstring){ var d = document.createElement('div'); d.innerHTML = HTMLstring; var docFrag = document.createDocumentFragment(); while (d.firstChild) { docFrag.appendChild(d.firstChild) }; return docFrag; }
Функция компактно упаковывает весь HTML текст в объект типа DocumentFragment. Кстати метод range.insertNode как и многие другие методы вставки нового нода в дерево DOM прекрасно переваривают DocumentFragment.
Признаться к своему стыду я не обнаружил метода получения HTML из объекта DocumentFragment. Может быть он и есть, но я его не нашел. поэтому пришлось написать обратную toDom функцию. Назовем ее toHTML
function toHTML(docFragment){ var d = document.createElement('div'); d.appendChild(docFragment); return d.innerHTML; }
Тут все просто. Я же говорил что DocumentFragment прекрасно едят все методы которые работают с обычными элементами дерева DOM
Постепенно мы подошли ко второй части нашей задачи - это перевод.
API Google предельно просто и понятно, сильно на нем заострять внимание не будем лишь для справки покажу как как это делается. Для того чтобы нам было доступно API переводчика нам нужно в код страницы включить вот следующий код:
<script type="text/javascript" src="http://www.google.com/jsapi"></script> <script> google.load("language", "1"); </script>
Вот и все переводчик готов к работе. Далее собственно сам перевод
google.language.translate('hello','en','ru',function (data){ alert(data.translation) // выводит привет })
Функция которая подается в качестве одного из параметров вызывается после завершения перевода, и все дальнейшие действия с переведенным текстом надо вешать на нее.
Ну и последний шаг нашей "кампании" это замена. Сразу приведу код
if(!ie){ var rang= selectedText.getRangeAt(0); rang.deleteContents() if($.browser.mozilla){ var rangeObj = document.createRange(); var documentFragment = rangeObj.createContextualFragment(data.translation); }else{ var documentFragment = toDOM(data.translation) } rang.collapse(false) rang.insertNode(documentFragment); }else{ selectedText.pasteHTML(data.translation) }
Этот код можно протестировать на примере номер 2
Для Мозилы я использовал метод createContextualFragment у объекта типа range. Данный метод есть и у других представителей рода "Браузерных", но как-то не очень хорошо он у них работает . Метод делает то для чего была написана функция toDOM - она кстати работает и для Мозилы тоже, так что createContextualFragment приведен просто для общего развития ;),
Метод rang.collapse(false), можно опустить если вы хотите просто заменить текст,но я использовал ее не просто так. Если вы к примеру захотите сделать чтобы переведенный текст не заменял выделенный, то удалите вызов метода - rang.deleteContents() и в зависимости от того какой параметр подается в rang.collapse true или false вставка произведется в конец или в начало выделения соответственно.
Чтобы материал легче усвоился посмотрите готовые примеры
Пример 1 - просто получаем выделенный текст и html со страницы
Пример 2 - автоматически переводим выделенный на странице текст с помощью Google Translate AJAX API
Пример 3 - тоже самое что и пример 2 только работа ведется с фреймом, это актуально для WYSIWYG редакторов, и отличается рядом нюансов
пример 4 - Все то же самое? но с использованием написанного мной плагина на jQuery
Комментарии
спасибо