Генерация текста из шаблонов
Создание HTML-кода для отображения выходных данных и создания диалоговых окон может оказаться процессом достаточно трудоемким, особенно если приходится генерировать большие блоки кода. Если это статический текст, то внедрить его в код скрипта совсем нетрудно, в предыдущей статье я уже приводил код, с помощью которого это можно легко сделать.
Код javascript | Выделить |
gClipboard.write(JSON.stringify(gClipboard.read()));
За основу я взял формат шаблона T4, точнее его директивы <# #> и <#= #>, другие нам не понадобятся. Идея такая: на основе синтаксиса T4 создается фрагмент кода, в котором текст смешан с кодом JavaScript и далее из этого шаблона создается генерирующая функция, которую можно будет использовать в коде кнопок для создания текста. Для преобразования шаблона в код мы создадим кнопку, которая код шаблона в буфере обмена будет заменять кодом функции-генератора. Код такой кнопки достаточно прост
Код javascript | Выделить |
String.prototype.trim = function () { return
this.replace(/(^\s+)|(\s$)/g, "");
}
function transformTemplate(template)
{
var blocks = template.replace(/(\<#[\s\S]*?#\>|\<#=[\s\S]*?#\>)/gm,
"\u001f$1\u001f").split("\u001f");
var rv = "function(args)\n{\n\tvar returnValue = \"\";\n";
var stmre = /\<#([\s\S]*?)#\>/,
expre = /\<#=([\s\S]*?)#\>/;
for (var i = 0;
i < blocks.length; i++)
{
if (expre.test(blocks[i]))
{
var b = blocks[i].replace(expre, "$1").trim()
rv += b.length > 0 ? "\treturnValue += " + blocks[i].replace(expre, "$1").trim()
+ ";\n" : "";
}
else if
(stmre.test(blocks[i]))
{
var b = blocks[i].replace(stmre, "$1").trim();
rv += b.length > 0 ? "\t" + b + ";\n"
: "";
}
else
{ rv += "\treturnValue += " + JSON.stringify(blocks[i]) + ";\n"
}
}
rv += "\treturn returnValue;\n}\n";
return rv;
}
gClipboard.write(transformTemplate(gClipboard.read()));
<# for(var i = 0; i < args.length; i++){ #>
<option value="<#= i + 1 #>"><#= args[i] #></option>
<# } #>
</select>
Код javascript | Выделить |
function(args)
{
var returnValue = "";
returnValue += "<select>\n";
for(var i = 0; i
< args.length; i++){;
returnValue += "\n<option value=\"";
returnValue += i + 1;
returnValue += "\">";
returnValue += args[i];
returnValue += "</option>\n";
};
returnValue += "\n</select>";
return returnValue;
}
Код html5 | Выделить |
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8"
/>
<title>Тестирование
сгенерированной функции</title>
</head>
<body>
<div id="qqq"></div>
<script>
var createSelect = function(args)
{
var returnValue = "";
returnValue += "<select>\n";
for(var
i = 0; i < args.length; i++){;
returnValue += "\n<option value=\"";
returnValue += i + 1;
returnValue += "\">";
returnValue += args[i];
returnValue += "</option>\n";
};
returnValue += "\n</select>";
return returnValue;
}
var qqq = document.getElementById("qqq");
qqq.innerHTML = createSelect("Понедельник,Вторник,Среда,Четверг,Пятница,Суббота,Воскресенье".split(","));
</script>
</body>
</html>
Кроме того, можно таким образом создавать отдельные блоки, а потом в других шаблонах вызывать функции, полученные из этих блоков и так собирать документ из отдельных блоков.
Преобразование страницы с помощью XSLT
Зачастую (чтобы не сказать "как правило") формировать HTML приходится на основе страницы, загруженной в активной вкладке браузера. В качестве примера можно взять описание загрузки аудио из вконтактика, о котором я писал здесь
Качаем видео и музыку из "вконтактика".
Там собирались данные со страницы вконтактика с аудиотреками и из них формировалась страница со ссылками, которые потом можно было загрузить с помощью расширения DownThemAll. В противовес чистому JavaScript-решению можно решить эту же задачу с помощью XSLT. Это декларативный язык и на нем формировать HTML намного удобнее, особенно, если речь идет о более-менее сложном коде. При этом JavaScript-код, который будет все это обрабатывать, отличаться будет только значением переменной, содержащей XSLT-код. Для решения задачи создания ссылок на аудиофайлы вконтактика создадим XSLT
Код xml | Выделить |
<?xml version="1.0"
encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="html">
<xsl:output method="html"
indent="yes"/>
<xsl:template match="/">
<html>
<head>
<title>Ссылки на аудиофайлы</title>
<meta charset="utf-8"/>
</head>
<body>
<xsl:for-each select="//html:div[contains(@class,
'audio') and contains(@class, 'fl_l')]">
<div>
<a>
<xsl:attribute name="href">
<xsl:value-of select="substring-before(.//html:input[starts-with(@id,
'audio_info')][1]/@value, ',')"/>
</xsl:attribute>
<xsl:value-of select=".//html:div[contains(@class,
'title_wrap')]"/>
</a>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Теперь преобразуем его JavaScript-переменную и на ее основе создадим код кнопки
Код javascript | Выделить |
function opentTransformedPage(xsltCode)
{
var xsltProc = new
XSLTProcessor();
var parser = new
DOMParser();
var xsltDoc = parser.parseFromString(xsltCode, "application/xml");
var serializer = new
XMLSerializer();
var pageCode = serializer.serializeToString(content.document.documentElement);
var pageXml = parser.parseFromString(pageCode, "application/xml");
xsltProc.importStylesheet(xsltDoc);
var resultDoc = xsltProc.transformToDocument(pageXml);
var resultCode = serializer.serializeToString(resultDoc);
var newwin = content.open("about:blank");
newwin.onload = function (evt)
{
newwin.document.documentElement.innerHTML = resultCode;
};
}
var xsltCode = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:html=\"http://www.w3.org/1999/xhtml\" xmlns=\"http://www.w3.org/1999/xhtml\"\n exclude-result-prefixes=\"html\">\n <xsl:output method=\"html\" indent=\"yes\"/>\n\n <xsl:template match=\"/\">\n <html>\n <head>\n
<title>Ссылки на аудиофайлы</title>\n <meta charset=\"utf-8\"/>\n </head>\n <body>\n <xsl:for-each select=\"//html:div[contains(@class, 'audio') and contains(@class, 'fl_l')]\">\n <div>\n <a>\n <xsl:attribute name=\"href\">\n <xsl:value-of select=\"substring-before(.//html:input[starts-with(@id,
'audio_info')][1]/@value, ',')\"/>\n </xsl:attribute>\n <xsl:value-of select=\".//html:div[contains(@class, 'title_wrap')]\"/>\n </a>\n </div>\n </xsl:for-each>\n </body>\n </html>\n </xsl:template>\n\n</xsl:stylesheet>\n";
opentTransformedPage(xsltCode);
Другой пример, когда нам может понадобиться XSLT - копирование страницы или ее части. Например, если мы хотим скопировать статьи из своего блога на киберфоруме в другое место, скажем в blogger или еще куда. Можно, конечно, скопировать и вставить или скопировать нужную часть HTML-кода, но в этом случае будет иметь место ряд неприятных моментов. Во-первых, там перестанут работать спойлеры, "Выделить код", да и мало ли что еще. Все это неплохо бы убрать или переформатировать так, чтобы оно там работало. Во-вторых, исчезнет форматирование текста, поскольку правила стилей применяются к элементам в соответствии с селекторами, да и описаны они где-то за пределами того фрагмента, который копируется.
В данном случае можно решить проблему тем, что преобразованию будет подвергнута не сама страница, а клон, либо всей страницы, либо какого-то ее фрагмента. Создав клон с помощью cloneNode, его можно как следует обработать прежде чем подвергать сериализации. Например обойти рекурсивно все элементы и назначить им явно вычисленные правила стиля. Кроме того, иногда XMLSerializer сериализует некорректный документ, после этого XSLTProcessor не может его обработать. То есть бывает полезно перед выполнением преобразования сначала как-то обработать документ в JavaScript-коде. Вот пример такого решения
Код javascript | Выделить |
var rules = "color,font-size,font-family,font-weight,font-style".split(",")
function getCssText(element)
{
var css = ""
var compStyle = content.getComputedStyle(element);
for (var i = 0;
i < rules.length; i++)
{
css += rules[i] + ": " + compStyle[rules[i]] + ";";
}
return css;
}
function processPageClone(node)
{
var pclone = node.cloneNode(true);
var elements = pclone.querySelectorAll("*");
for (var i = 0; i <
elements.length; i++)
{
elements[i].setAttribute("style", getCssText(elements[i]));
}
return pclone;
}
function openNewPage(pageCode)
{
var newwin = content.open("about:blank");
newwin.onload = function (evt)
{
newwin.document.body.innerHTML = pageCode;
};
}
function getTransformedPage(xsltCode, fragmentSelector)
{
var xsltProc = new
XSLTProcessor();
var parser = new
DOMParser();
var xsltDoc = parser.parseFromString(xsltCode, "application/xml");
var serializer = new
XMLSerializer();
var pageClone = processPageClone(content.document.querySelector(fragmentSelector));
var pageCode = serializer.serializeToString(pageClone);
gClipboard.write(pageCode);
var pageXml = parser.parseFromString(pageCode, "application/xml");
xsltProc.importStylesheet(xsltDoc);
var resultDoc = xsltProc.transformToDocument(pageXml);
var resultCode = serializer.serializeToString(resultDoc);
return resultCode;
}
var xsltCode = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:html=\"http://www.w3.org/1999/xhtml\"\n exclude-result-prefixes=\"html\">\n <xsl:output method=\"html\" indent=\"yes\"/>\n\n <xsl:template match=\"@* | node()\">\n <xsl:copy>\n <xsl:apply-templates select=\"@* | node()\"/>\n
</xsl:copy>\n </xsl:template>\n\n </xsl:stylesheet>\n";
openNewPage(getTransformedPage(xsltCode, "#blog_message"));
Другой вариант - обрабатывать всю страницу, но в processPageClone вносить коррективы в документ. Например в данном случае неправильно обрабатываются фреймы, но они и не нужны, так что можно их просто удалить. А нужный блок страницы будет выбираться уже в преобразовании как в следующем примере
Код xml | Выделить |
<?xml version="1.0"
encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:html="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="html">
<xsl:output method="html"
indent="yes"/>
<xsl:template match="@*
| node()">
<xsl:copy>
<xsl:apply-templates select="@*
| node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<div>
<xsl:apply-templates select="//*[@id='blog_message']"/>
</div>
</xsl:template>
</xsl:stylesheet>
Сам код кнопки
Код javascript | Выделить |
var rules = "color,font-size,font-family,font-weight,font-style".split(",")
function getCssText(element)
{
var css = ""
var compStyle = content.getComputedStyle(element);
for (var i = 0;
i < rules.length; i++)
{
css += rules[i] + ": " + compStyle[rules[i]] + ";";
}
return css;
}
function processPageClone(node)
{
var pclone = node.cloneNode(true);
var frames = pclone.querySelectorAll("iframe");
for (var i = 0;
i < frames.length; i++)
{
var f = frames[i];
f.parentElement.removeChild(f);
}
var elements = pclone.querySelectorAll("*");
for (var i = 0; i < elements.length; i++)
{
elements[i].setAttribute("style", getCssText(elements[i]));
}
return pclone;
}
function openNewPage(pageCode)
{
var newwin = content.open("about:blank");
newwin.onload = function (evt)
{
newwin.document.body.innerHTML = pageCode;
};
}
function getTransformedPage(xsltCode)
{
var xsltProc = new XSLTProcessor();
var parser = new
DOMParser();
var xsltDoc = parser.parseFromString(xsltCode, "application/xml");
var serializer = new
XMLSerializer();
var pageClone = processPageClone(content.document);
var pageCode = serializer.serializeToString(pageClone.documentElement);
var pageXml = parser.parseFromString(pageCode, "application/xml");
xsltProc.importStylesheet(xsltDoc);
var resultDoc = xsltProc.transformToDocument(pageXml);
var resultCode = serializer.serializeToString(resultDoc);
return resultCode;
}
var xsltCode = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:html=\"http://www.w3.org/1999/xhtml\"\n exclude-result-prefixes=\"html\">\n <xsl:output method=\"html\" indent=\"yes\"/>\n\n <xsl:template match=\"@* | node()\">\n <xsl:copy>\n <xsl:apply-templates select=\"@* | node()\"/>\n
</xsl:copy>\n </xsl:template>\n\n <xsl:template match=\"/\">\n <div>\n <xsl:apply-templates select=\"//*[@id='blog_message']\"/>\n </div>\n </xsl:template>\n\n</xsl:stylesheet>\n";
openNewPage(getTransformedPage(xsltCode));
Также может понадобиться исправить ссылки. Например, локальные ссылки (внутри документа) могут быть абсолютными, что при переносе не есть хорошо. То же касается ссылок на другие документы, которые также могут быть перенесены, их лучше сделать относительными и заменить новыми адресами. Приведу пример, где локальные ссылки исправляются, в XSLT удалены спойлеры и ссылки "Выделить код", которые не будут работать на другом ресурсе без соответствующих скриптов.
Код javascript | Выделить |
var rules = "color,font-size,font-family,font-weight,font-style".split(",")
function getCssText(element)
{
var css = ""
var compStyle = content.getComputedStyle(element);
for (var i = 0;
i < rules.length; i++)
{
css += rules[i] + ": " + compStyle[rules[i]] + ";";
}
return css;
}
function correctLink(a)
{
var href = content.document.location.href;
var ahref = a.getAttribute("href");
if (ahref && ahref.startsWith(href + "#"))
{
var splHref = ahref.split("#");
a.setAttribute("href", (splHref.length > 1 ? "#"
+ splHref[1] : ""));
}
}
function processPageClone(node)
{
var pclone = node.cloneNode(true);
var frames = pclone.querySelectorAll("iframe");
for (var i = 0;
i < frames.length; i++)
{
var f = frames[i];
f.parentElement.removeChild(f);
}
var elements = pclone.querySelectorAll("*");
for (var i = 0;
i < elements.length; i++)
{
var el = elements[i];
el.setAttribute("style", getCssText(el));
if (el.tagName == "A")
{
correctLink(el);
}
}
return pclone;
}
function openNewPage(pageCode)
{
var newwin = content.open("about:blank");
newwin.onload = function (evt)
{
newwin.document.body.innerHTML = pageCode;
};
}
function getTransformedPage(xsltCode)
{
var xsltProc = new
XSLTProcessor();
var parser = new
DOMParser();
var xsltDoc = parser.parseFromString(xsltCode, "application/xml");
var serializer = new XMLSerializer();
var pageClone = processPageClone(content.document);
var pageCode = serializer.serializeToString(pageClone.documentElement);
var pageXml = parser.parseFromString(pageCode, "application/xml");
xsltProc.importStylesheet(xsltDoc);
var resultDoc = xsltProc.transformToDocument(pageXml);
var resultCode = serializer.serializeToString(resultDoc);
return resultCode;
}
var xsltCode = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:html=\"http://www.w3.org/1999/xhtml\"\n exclude-result-prefixes=\"html\">\n <xsl:output method=\"html\" indent=\"yes\"/>\n\n <xsl:template match=\"@* | node()\">\n <xsl:copy>\n
<xsl:apply-templates select=\"@* | node()\"/>\n </xsl:copy>\n </xsl:template>\n\n <xsl:template match=\"/\">\n <div>\n <xsl:apply-templates select=\"//*[@id='blog_message']\"/>\n </div>\n </xsl:template>\n\n <xsl:template match=\"html:div[@class='spoiler-wrap']\">\n <div style=\"width:90%;overflow:auto;\">\n <xsl:apply-templates select=\".//html:div[@class='spoiler-body']\"/>\n </div>\n </xsl:template>\n \n<xsl:template match=\"html:a[text()='Выделить
код']\"/>\n\n</xsl:stylesheet>\n";
openNewPage(getTransformedPage(xsltCode));
Код javascript | Выделить |
gClipboard.write(getTransformedPage(xsltCode));
Во всех приведенных примерах сам код XSLT сохранялся как статическая переменная, однако можно воспользоваться идеей изложенной в предыдущем параграфе и сохранять XSLT, созданный из динамического шаблона в виде функции. XSLTProcessor не дает нам возможности делать многое, что могут другие процессоры, например - передавать параметры в преобразование или расширять его скриптами и дополнительными функциями. Отчасти эту задачу можно решить путем программной генерации скрипта. Например, если преобразование создается из шаблона, то передать параметры можно так
Комментариев нет :
Отправить комментарий