Морфана: приставки, корни, суффиксы…

    image
    Совсем недавно мы столкнулись с необходимостью HTML-верстки большого количества разнообразного учебного (школьная программа) материала.

    К сожалению, в ряде случаев пришлось отдельно готовить изображения с текстовыми блоками, которые были снабжены хитрой версткой. Это усложняет процессы HTML-верстки и внесения правок. Благодаря замечательному MathJax, мы смогли оптимизировать верстку в части математики, физики и неорганической химии, однако органическая химия осталась непобежденной. Да, есть пакеты для верстки структурных формул в LaTex, но в MathJax их поддержки нет.

    Взять, к примеру, морфемный анализ слова (разбор слова по составу). Тут, увы, тоже готовых решений не нашлось… Встретились единичные случаи в Сети: некоторые сайты внедрили собственные решения, но несвободные и недокументированные. Кроме того, не очень удобные для верстки. В остальных же случаях (которых большинство) — опять-таки вставка заранее сделанных изображений. Почти всегда — растр.

    Поэтому, готовясь к новым материалам, а также давно желая попробовать разработку на GitHub, я решил на досуге сделать простенький js-движок для отрисовки условных обозначений морфем. Работа сырая, но я буду рад, если кто-то присоединится, покритикует код (разумеется, там есть что критиковать) или вспомнит при необходимости о Морфане в будущем.

    Позже планируется также библиотека для отображения условных обозначений синтаксического разбора предложения.

    UPD: Демо здесь
    Поделиться публикацией
    Комментарии 41
      +3
      Удручает далекий от международного DSL: ko, ok мало что скажут англоговорящему программисту, которому захочется воспользоваться такой замечательной наработкой.

      var queue = new Array(); — зачем new Array? Всегда пишите var queue = [];
      var tmpdv = $('<div style="' + ((debug)?"":"left: -1000px; visibility: hidden; border: 2px solid blue; ") + 'width: auto; height: auto; position: absolute;" id="tmpdv" />') — временный div создается каждый раз при вызове функции — не бог весть какая нагрузка, но все же дешевле создать его заранее, а в этом месте только клонировать.
      newNode = document.createElement('span'); jQuery(newNode).css('letter-spacing','normal'); — то же самое.
      Удручает так же отсутствие поддержки AMD/UMD.

      В остальном штука нужная и полезная, спасибо!
        0
        Благодарю за детали!
        Повторяемость кода — согласен. Практически ничего не оптимизировал еще. А насчет того, что клонирование дешевле, чем создание — удивлен! Попробую.

        Насчет DSL — спорно. С одной стороны код и комментарии привел (пока не до конца) в состояние, понятное англоязыному. Пока испытываю сложности с переводом специфических терминов и в целом пишу слабо, только читаю. Но эта работа сделана будет. А вот касательно названий морфем — тут я как раз специально остановился на сокращенных транслитах. Бибилиотека всё-таки нужна только для русского языка. Не поверите — до сих пор не смог найти никакого стандарта по условным обозначениям. Что уж говорить про зарубежье. А в серьезной науке уже не пользуются таким обозначениями — только в школе. Поэтому, я нацелен сделать библиотеку максимально простой для рядового пользователя, который чуть-чуть знает HTML. Если библиотека будет востребована, то по двум направлениям: сайты по русскому языку, электронные издания по русскому языку (аналоги EPUB.)

        Насчет AMD/UMD стоит подумать, согласен. Но пока надо доработать функционал. Хотелось бы все-таки охватить MSIE8, добавить некоторые специфические условные обозначения и реализовать кое что в API для удобства создания интерактивов (редактирования разметки слова с помощью инструмента выделения)
          0
          Сделал PR с враппером, думать там нечего, механическая работа.

          Насчет терминов все-таки не соглашусь. Когда работаешь с кодом, мозг автоматически переходит на английский язык, а тут вдруг какой-то nullok нна тебе когнитивным диссонансом по организму:)
            0
            О :) Тут вы правы! nullok — не относится к части «команд» для разметки. Это внутренний «термин». Надо, конечно, привести в порядок :)
            +1
            А в IE8 вы намучаетесь с VML, к сожалению:(
              0
              Видимо да. Хотя есть надежда на keith-wood.name/svg.html
                –1
                Это не то, что вам нужно…
                  0
                  Возможно я перепутал ссылки. Когда я наткнулся на работающее решение на сайте русский-на-5, я себе просто отметил, что есть некий простой способ адаптировать под MSIE8.

                  В крайнем случае буду ковырять Cufon, MathJax и разбираться с VML.

                  Но ситуация такова: в моей основной работе мне приходится ориентироваться в основном на WebKit. Так что, потихоньку.
          +1
          Текст внутри окончаний не выделяется мышью (по крайней мере, в Chrome).
          Есть ли возможность изменять стиль обозначений? Хотя бы цвет.
          Последний пример на сайте производит впечатление неправильной работы. Я далеко не сразу понял, что так и задумано.
          Как вызвать обновление обозначений динамически через JS?
          Пробовали делать реализацию средствами CSS вместо SVG? Может получиться проще.
            0
            — Да. Пока эту проблему решить не удалось. Решение с z-index:-1 привело к другой ошибке. Но идеи есть.
            — Пока нет, но обязательно будет. Цвет — точно.
            — Я так и думал :) А на самом деле он дан для того, чтобы показать, что сложная разметка самого слова не мешает нормальному определению размеров морфем. Т.е., не обязательно, чтобы в коде текст слова шел подряд и содержался в одной ноде. Надо снабдить комментарием, спасибо за акцент!
            — API есть, но пока, пардон, не задокументирован полноценно. В конфигурации описан запрет автозапуска, а следовательно имеется возможность запустить обработку страницы целиком, конкретных узлов и, в том числе передав в вызов новую разметку.
            — Сперва вообще начал с canvas. А средствами CSS — без специального шрифта? Без шрифта не представляю как. И не получится ли хуже по совместимости?
            +1
            А вы думали о реализации на голом CSS?

            Например:
              <span class="word"><span class="prefix">вы</span><span class="suffix">ну</span><span class="suffix">ть</span></span>
            


              +1
              Вы знаете, подобный вариант я встретил на одном сайте. И все-таки выбрал вариант с «малоинвазивной» разметкой через атрибут.

              Тут вот какое дело:
              1) морфемы могут пересекаться. Кстати, чем дальше я влезаю в предметную область, тем больше удивляюсь :)
              2) в моем варианте, imho, удобнее и понятнее сама разметка. А библиотека ориентирована на начинающего пользователя.
              3) проще автоматизировать разметку, если прицепиться к сервису, который по словарю делает разбор.
              4) проще код интерактива, который позволяет вживую разметить. Он готов, но есть непонятные баги, поэтому не выложен.

              Сейчас библиотека лезет в DOM минимально: добаляет svg и span-ы вокруг букв окончаний, чтоб сделать отступы.
              В ближайшее время придется добавить что-то еще, чтобы svg элементы положить под слово. Тогда текст в окончании будет кликабельным. Это к предыдущему каменту, кстати.
                0
                Я имел ввиду на чистом CSS, как-то так
                  0
                  Ага. Честно говоря, я сразу такую идею отмел. А после, когда столкнулся с пересекающимися мофемами — убедился, что правильно сделал.
                  Такой вариант, как вы предлагаете работает на сайте, который я встретил уже после начала разработки: russkiy-na-5.ru/articles/219

                  Так вот, если морфемы перескаются, как мне одну и ту же букву запихнуть в два разных span-а? Не вложенных, как в случае, когда корень и суффикс входя в основу. А именно пересекающихся.

                  И потом, согласитесь, что объяснить как выполнить разметку слова через строчку в атрибуте (наглядно видно номера букв!) проще, чем span-ы… Ну, мои куцые «su» и «ko» не берём в расчёт :-)

                  Хотя, конечно, да, проблем с определением размером морфем и позиционирования svg не было бы :)
              0
              А почему был использован SVG, а не canvas (не холивара ради)?
                0
                Ну как-то не комильфо: текст при масштабировании не теряет привлекательности, а значки морфем — да.
                И, насколько я понял, с точки зрения кроссбраузерности почти одинаковая ситуация.
                  0
                  Потому что это верный путь. А через CSS стили накидать можно и на SVG.
                  image
                    0
                    Вот кстати даже не подумал об этом :)
                    Я недавно еще большей частью во Flash/AS3, поэтому голова не всегда включается, что SVG тоже в DOM.
                      0
                      Но аккуратнее: не все умеют так, поэтому лучше сейчас оставить всё как есть и добавить CSS для оформления всего этого дела.
                  0
                  Спасибо за MathJax, кстати
                    0
                    Дык пожалуйста! Сам рад неимоверно.
                    Что особенно хорошо — поддержка TeX. У нас часто исходники — DOC-файлы с объектами MathType
                    Так что вытаскивать формулы было удобно. Ну и с TeX-ом знакомы немного.
                    0
                    На странице описания синтаксиса для окончания и основы используется одно и то же ok, и это как-то не ок.
                    И вообще двухбуквенный синтаксис выглядит как cheat sheet. Читать такое сложно. Может быть, стоило бы использовать английские слова root, prefix, suffix, end, basis?
                      0
                      Спасибо, поправлю опечатку.
                      Может быть вы правы. Но я ориентировался на транслит.
                      Насчет двухбуквенности — хотелось лаконичности, но сейчас уже вылезают проблемы с тем, что двух букв будет недостаточно.
                      Дело в том, что условные обозначения для морфем различаются в разных источниках.
                      Не всегда могут договориться, что какой морфемой является, а уж обозначения — тем более. Никакого стандарта нет.
                      Более того, в ряде источников приводятся корни, которые отмечаются снизу слова. Это как раз для случая пересечений морфем.
                      Я некоторые время погуглил, поспрашивал знакомых редакторов-предметников, профильные форумы.
                      Толку мало. Набираешь разные школьные словари и учебники, интегрируешь и всё реализуешь.
                      Так что, может быть, как устаканится — можно будет добавить и такой синтаксис команд.

                        +3
                        Поддерживаю: использовать нужно английские слова, желательно полные или понятно сокращённые. Вы, таким образом, расширите потенциальную применимость библиотеки.
                          0
                          Согласен, что глаз режет.
                          Думаю, введу синонимы, как только дойдут руки проконсультировать насчет грамотного перевода терминов.
                          Но насчет расширения применимости — вряд ли. Такие условные обозначения, imho, используются только в России, ну, может, где-то еще в СНГ (Украина, Беларусь) Всё-таки раньше учебная программа была целостной на территории СССР.
                      +1
                      Статья не отпустила, пока не придумал слово, подходящее к картинке с морфемами.
                        0
                        :-D Уверены в правильности?
                        При окунании в предметную область я сталкиваюсь с удивительными вещами. Например, есть такая вещь — современное состояние языка. Случается, что приставки сростаются с корнями. Или вот в слове «обесценивать» — кто-то выделяет две приставки, а оказывается — одна «обес».
                        Интересного много.
                        0
                        Предлагаю рассмотреть возможность использования для верстки учебного материала мой чудесный проект Markdown webdocs. Для расширения разметки и решения таких задач как у вас есть все возможности, полностью компонентная система, а по сравнению с нативным html упростится написание и обслуживание базы документов.
                          0
                          Спасибо, посмотрю внимательно. Но, после беглого ознакомления, не понял — к специфической верстке, для которой у нас используется MathJax и вот теперь Морфана, оно — как?

                          В принципе, у нас нет проблем с версткой материалов в целом. Развиваем классификацию материалов, делаем шаблоны. Потихоньку совершенствуем. Вот таких штук, про которые я написал в посте немного, но они есть. Органическая химия — самая проблема :)

                          P.S. Я — за решения WYSIWYM!
                            0
                            Оно потенциально к любой верстке. C MathJax есть пример с ленивой загрузкой в разделе компоненты. Морфемы легко. Любой подход или библиотеку можно легко обернуть в виде компонента и затем использовать с ленивой загрузкой в верстке и со статической в продакшене.

                            P.S. Я — за решения WYSIWYM!

                            html это ассемблер веба, html страшно далек от этого принципа, и при росте количества страниц осмысленного текста и с необходимостью правок если у вас будет использоваться html, шаблоны, вы погрязнете. А разметки форматирования значительно ближе к этому принципу.

                            И с органической химией наверное можно было бы что-то придумать, надо бы конкретный пример.
                              0
                              Согласен, поэтому и используем подобные решения.
                              По органике — вот примеры: tex.stackexchange.com/questions/52722/can-you-make-chemical-structure-diagrams-in-latex
                              А насчет предлагаемого вами — оно хорошо в нашем случае: меньше «ассемблера», выше уровень абстракции, но конкретно отрисовка условных обозначений морфем или структурных формул решаются другими библиотеками?
                              У нас в их отсутствии загвоздка, а не в качественном комплексном шаблонизаторе во фронтенде, вот в чем дело.
                                0
                                Хм, структурные формулы сейчас это просто, полно низкоуровневых библиотек. Могли бы договориться — я вам структурные формулы, а вы обязательства по использованию моей технологии в своих проектах :)
                          0
                          в чем сакралный смысл подключать конфиги так:
                          <script type="text/x-morfana-config">Morfana.configure({autoStart: false, freezeWord: false});</script>
                          

                          И потом делать так:
                          var scripts = document.getElementsByTagName("script");
                                  for (var i = 0, qty = scripts.length; i < qty; i++) 
                                  {
                                      var type = String(scripts[i].type).replace(/ /g,"");
                                          if (type.match(/^text\/x-morfana-config(;.*)?$/)) 
                                          {
                                                  window.eval(scripts[i].innerHTML);
                                                  scripts[i].innerHTML = '';
                                          }
                              }
                          
                          

                          ?
                            0
                            Ну, подглядел у MathJax и упростил. Они, правда не затирают, а ставят отметку о факте обработки блока конфига. А сам код пихают в очередь обработки, а я сразу в eval.

                            А в чём криминал?

                            У меня была задача — минимум кода для дефолтного варианта использования библиотеки: подгрузилось — само запустилось — готово.
                            Для пользователя с минимумом требований.

                            А вот если нужно конфигурировать, то сперва нужно запретить автозапуск.
                            Был еще вариант — дописывать в script в путь к js-файлу параметры. Также делает MathJax. Но это, imho, хуже. И всё равно парсить и обрабатывать.

                              0
                              Я не вижу в чем сложность вот это:
                              <script type="text/x-morfana-config">Morfana.configure({autoStart: false, freezeWord: false});</script>
                              

                              заменить на это:
                              <script>Morfana.configure({autoStart: false, freezeWord: false});</script>
                              

                              И вообще выкинуть кусок с eval. Зачем он нужен?
                                0
                                Хм. Возможно дело в моем непонимании порядка работы интерпретатора.
                                Если добавить код конфига в виде js-скрипта после кода подгрузки библиотеки — не получится. Если после — будет ОК.
                                Но я не понимаю, почему вообще такое возможно?
                                Я подключаю библиотеку как
                                (function(window){/* ... */})(window);
                                Разве она не должна начать своё выполнение сразу по готовности скриптов?
                                С одной стороны уже есть глобальный объект Morfana, иначе не работал бы код конфигурации, с другой стороны — автостарта не происходит…
                                Как это может быть?
                                Здесь не может «race condition»? Т.е., если я пропишу код конфигурации строго после пдогрузки библиотеки, я могу быть уверен, что всегда будет работать?
                                  0
                                  Все верно, код начинает работать сразу после загрузки, но у вас блок инициализации и чтения конфигов, расположен внутри:
                                  jQuery(document).ready(function(){ ....  });
                                  

                                  А это значит что вы начинаете работать в момент когда загружены все скрипты, стили и картинки, вставленные на странице.
                                  И по этому если подключить сначала библиотеку а потом ее конфиги, то часть кода с eval можно выкинуть.

                                  И вот эта функция:
                                  // DOM ready
                                  jQuery(document).ready(function(){
                                          // reading user config
                                             var scripts = document.getElementsByTagName("script");
                                          for (var i = 0, qty = scripts.length; i < qty; i++) 
                                          {
                                              var type = String(scripts[i].type).replace(/ /g,"");
                                                  if (type.match(/^text\/x-morfana-config(;.*)?$/)) 
                                                  {
                                                          window.eval(scripts[i].innerHTML);
                                                          scripts[i].innerHTML = '';
                                                  }
                                      }
                                  
                                          // rangy init
                                          rangy.init();
                                          
                                          // autostart if not denied by user
                                          if (config['autostart']){draw();}
                                          
                                  });
                                  


                                  Сократиться до:
                                  // DOM ready
                                  jQuery(document).ready(function(){
                                          // rangy init
                                          rangy.init();
                                          
                                          // autostart if not denied by user
                                          if (config['autostart']){draw();}        
                                  });
                                  
                            0
                            Ого, какой мощный у вас подход. SVG, нагруженный JS, все дела.

                            А зачем так сложно? Почему не сделать на чистом CSS, а в JS оставить только морфологический разбор? Вот, наклепал proof of concept на CSS: sassmeister.com/gist/7328321

                            Конечно, не так красиво получается, как у вас, но ведь я потратил на это всего полчасика. С обозначением суффикса возникли неожиданные сложности, которые я так и не решил, но при желании можно допилить и сделать красиво.
                              +1
                              А! Вот такой «чистый CSS» — более приемлемый вариант. Разумеется, можно было сделать динамическую разметку span-ами, а не позиционировать svg-шки, как я сделал, но… смотрите:

                              Оставлять разметку span-ами на человека я не хотел — это усложняет ему задачу и, кроме того, пересекающиеся морфемы так не сделать (хотя, каюсь, я тогда о них не знал).

                              Допустим, я выбрал предлагаемый вами вариант и не думаю о пересечении морфем. Следовательно, основная задача библиотеки — правильно расставить span-ы. И вот тут я, конечно, нарушаю KISS, но предполагаю, что <span>слово</span> — это идеальный вариант. А может быть такой: <span>сл<b>о</b>во</span>. И, поверьте, такое встречается часто. Отмечают орфограммы в словах и тут же делаю морфемный разбор.

                              Я иду дальше, предполагаю, что разметка в слове может быть сложной и, главное, может статься так, что буквы одной морфемы лягут в разные элементы. И тогда я не смогу обернуть их в один span. Ну, например, в <span>пищевар<b>ени</b>е</span> надо выделить окончание «ие». Надуманно? Возможно. Но я пошел по такому пути. И позиционировал svg без span-ов. Тем более, если пересекающиеся мофемы — не редкость (я не знаю материалов школьной программы с углубленным изучением русского языка), то мой подход оправдан. А динамический разбор слова по номерам букв делается просто, строчными функциями — если слово написано сплошняком, без HTML. А если нет — тогда либо громоздить свой код, либо использовать Range (для этого там rangy собственно и исользуется).

                              Следует отметить, что я все-таки оборачиваю в span-ы некоторые буквы. А именно — первую и последнюю букву морфемы «окончание». Из-за её специфического вида нужны отступы. Но обратите внимание — только первую и последнюю (или одну — для однобуквенных окончаний). Раздельно. Работая на уровне единичных символов. Т.е. я всегда смогу обернуть букву в span.
                              0
                              Сделайте ссылку в статье на официальный сайт, чтобы демо можно было сразу щупать.

                              В Опере 12.16 у меня что-то не так: prntscr.com/228tvu.

                              Помочь вопросу выделения окончаний имхо поможет CSS-свойство pointer-events: none, примененное на SVG-элементы (все браузеры, кроме IE до 10 включительно).

                              Идея хорошая, удачи вам с проектом!)
                                0
                                Ага, видел при тестировании на browsershots такое. Заливка контуров непрозрачна. Буду править.

                                Про pointer-events не слышал. Посмотрю, спасибо. Если это аналог mouseEnabled/mouseChildren из Flash — замечательно.

                                Спасибо!

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое