Мотивация и проблема
Есть несколько библиотек для генерации строк разметки html с использованием ruby (erb, haml, builder, markaby, tagz, ...), но я не удовлетворенс любым из них.Причина в том, что, за исключением erb, они используют стиль вложенности, а не стиль цепочки.И erb - это способ встроить ruby в html, а не генерировать html с помощью ruby.
Насколько я понимаю, одна прелесть ruby заключается в поощрении использования стиля цепочки:
receiver.method1(args1).method2(args2). ... method_n(args_n)
выполнения стиля вложения:
method_n(...method2(method1(receiver, args1), args2), ... args_n)
Но библиотеки, упомянутые выше (кроме erb), используют стиль вложения (иногда с помощью аргументов блока).
Моя идея
Для своих собственных целей я написал метод dom
, чтобы я мог выполнять разметку html в стиле цепочки.Применительно к строке этот пример
"This is a link to SO".dom(:a, href: "http://stackoverflow.com").dom(:body).dom(:html)
сгенерирует:
<html><body><a href="http://stackoverflow.com";>This is a link to SO</a></body></html>
При применении к массиву это:
[
["a".dom(:td), "b".dom(:td)].dom(:tr),
["c".dom(:td), "d".dom(:td)].dom(:tr)
].dom(:table, border: 1)
сгенерирует
<table border="1";>
<tr>
<td>"a"</td>
<td>"b"</td>
</tr>
<tr>
<td>"c"</td>
<td>"d"</td>
</tr>
<table>
И, при применении без явного получателя (вне области строк и массивов),
dom(:img, src: "picture.jpg", width: 48, height: 48)
будет генерировать
<img src="picture.jpg";width="48";height="48";/>
Обратите внимание, что всесделано только одним методом dom
.Это намного проще, чем использование других библиотек.Он также гибок в том смысле, что на него не влияет изменение инвентаря HTML-тегов;Вы просто указываете это с помощью аргумента символа.В других библиотеках есть классы и / или методы для каждого тега.Кроме того, в отличие от эрба, это чистый рубин.Это не DSL, который необходимо преобразовать.
Моя реализация
Реализация выглядит следующим образом:
class Hash
def attribute
map{|k, v| %Q{#{k}#{
case v
when TrueClass; ''
when Hash; %Q{="#{v.subattribute}"}
else %Q{="#{v}"}
end
;}}}.join
end
def subattribute
map{|k, v| %Q{#{k}:#{v};}}.join
end
end
class Array
def dom type, hash = {}
"<#{type} #{hash.attribute}>\n#{join("\n").gsub(/^/, " ")}\n</#{type}>"
end
end
class String
def dom type, hash = {}
"<#{type} #{hash.attribute}>#{self}</#{type}>"
end
end
class Object
def dom type, hash = {}
"<#{type} #{hash.attribute}/>"
end
end
Вопросы
- Есть ли уже стабильные библиотеки, которые делают подобное?
- Каковы потенциальные проблемы этого подхода (особенно моей реализации или использования цепного подхода))?
- Некоторые атрибуты принимают логические значения, которые часто рекомендуется опускать.Например,
<input type="text";readonly>
вместо <input type="text";readonly="true">
.В моей нынешней реализации я могу сделать это, передав true
(который не будет использоваться в конце) в качестве значения для такого атрибута, как dom(:input, type: "text", readonly: true)
, но это кажется избыточным и также является частью причины, по которой я case
оператор в коде, что делает его медленнее.Есть ли лучший способ сделать это? - Есть ли возможные улучшения в реализации?