Входной I Have:Оптимизация tokenizing XSLT
Я работаю со списком Sharepoint, который производит RSS-каналы в следующем виде:
<?xml version="1.0"?>
<rss>
<channel>
<!-- Irrelevant Fields -->
<item>
<title type="text">Title</title>
<description type="html">
<div><b>Field1:</b> Value 1</div>
<div><b>Field2:</b> Value 2</div>
<div><b>Field3:</b> Value 3</div>
<div><b>Field4:</b> Value 4</div>
<div><b>Field5:</b> Value 5</div>
</description>
</item>
<item>
<title type="text">Title</title>
<description type="html">
<div><b>Field1:</b> Value 1</div>
<div><b>Field3:</b> Value 3</div>
<div><b>Field4:</b> Value 4</div>
<div><b>Field5:</b> Value 5</div>
</description>
</item>
<item>
<title type="text">Title</title>
<description type="html">
<div><b>Field1:</b> Value 1</div>
<div><b>Field2:</b> Value 2</div>
<div><b>Field3:</b> Value 3</div>
<div><b>Field4:</b> Value 4</div>
<div><b>Field5:</b> Value 5</div>
</description>
</item>
<!-- More <item> elements -->
</channel>
</rss>
Обратите внимание, что <description>
элемент кажется, определяет набор элементов. Кроме того, обратите внимание, что не все элементы <description>
содержат разметку для «Поле2».
Что мне нужно:
мне нужен XML следующего вида:
<?xml version="1.0"?>
<Events>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field2>Value 2</Field2>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
</Event>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field2/>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
</Event>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field2>Value 2</Field2>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
</Event>
</Events>
Правила (обновлено):
- Это должно быть XSLT 1.0 решение.
xxx:node-set
- единственная действительная функция расширения, доступная мне; это включает функции расширения, написанные на других языках, такие как C# или Javascript.- Если какая-либо информация о поле отсутствует, должен быть выведен пустой элемент. Обратите внимание, что в моем желаемом выводе пустой
<Field2>
дочерний элемент во втором элементе<Event>
. - Мы не можем предположить, что сами имена полей будут следовать любому конкретному шаблону; они так же могут быть
<PeanutButter>
,<Jelly>
и т.д.
Что я так далеко:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<Events>
<xsl:apply-templates select="*/item"/>
</Events>
</xsl:template>
<xsl:template match="item[contains(description, 'Field2')]">
<Event>
<xsl:variable name="vElements">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="description"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:variable>
<Category>
<xsl:value-of select="title"/>
</Category>
<xsl:apply-templates
select="exsl:node-set($vElements)/*[normalize-space()]" mode="token"/>
</Event>
</xsl:template>
<!-- NOTE HOW THIS TEMPLATE IS NEARLY IDENTICAL TO THE LAST ONE,
MINUS THE BLANK <Field2>; THAT'S NOT VERY ELEGANT. -->
<xsl:template match="item[not(contains(description, 'Field2'))]">
<Event>
<xsl:variable name="vElements">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="description"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:variable>
<Category>
<xsl:value-of select="title"/>
</Category>
<xsl:apply-templates
select="exsl:node-set($vElements)/*[normalize-space()]" mode="token"/>
<Field2/>
</Event>
</xsl:template>
<xsl:template match="*" mode="token">
<xsl:element
name="{substring-after(
substring-before(normalize-space(), ':'),
'<div><b>')}">
<xsl:value-of
select="substring-before(
substring-after(., ':</b> '),
'</div>')"/>
</xsl:element>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($text,$delimiter)">
<xsl:element name="token">
<xsl:value-of select="substring-before($text,$delimiter)"/>
</xsl:element>
<xsl:call-template name="tokenize">
<xsl:with-param
name="text"
select="substring-after($text,$delimiter)"/>
<xsl:with-param
name="delimiter"
select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$text">
<xsl:element name="token">
<xsl:value-of select="$text"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
... который производит:
<?xml version="1.0"?>
<Events>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field2>Value 2</Field2>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
</Event>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
<Field2/>
</Event>
<Event>
<Category>Title</Category>
<Field1>Value 1</Field1>
<Field2>Value 2</Field2>
<Field3>Value 3</Field3>
<Field4>Value 4</Field4>
<Field5>Value 5</Field5>
</Event>
</Events>
Есть два основных проблем с моим раствором:
- Он чувствует себя неуклюжим; есть повторяющийся код, и это кажется немного громоздким. Я думаю, что может произойти какая-то оптимизация?
- Обратите внимание, что он выводит пустые
<Field2>
элементы в неправильном порядке и размещает их внизу. Это несколько легко исправляется, я полагаю, но все мои решения кажутся глупыми и поэтому не включены. :)
Готов, Комплект, Идите!
Буду признателен за вашу помощь в более элегантном решении (или, по крайней мере, в решении, которое устраняет проблему № 2 выше). Благодаря!
Заключение
На основе наблюдений, сделанных @Borodin в своем решении, я решил пойти со следующим:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vFieldNames">
<name oldName="Field1" newName="fieldA" />
<name oldName="Field2" newName="fieldB" />
<name oldName="Field3" newName="fieldC" />
<name oldName="Field4" newName="fieldD" />
<name oldName="Field5" newName="fieldE" />
</xsl:variable>
<xsl:template match="/">
<events>
<xsl:apply-templates select="*/*/item" />
</events>
</xsl:template>
<xsl:template match="item">
<event>
<category>
<xsl:value-of select="title" />
</category>
<xsl:apply-templates select="exsl:node-set($vFieldNames)/*">
<xsl:with-param
name="pDescriptionText"
select="current()/description" />
</xsl:apply-templates>
</event>
</xsl:template>
<xsl:template match="name">
<xsl:param name="pDescriptionText" />
<xsl:variable
name="vRough"
select="substring-before(
substring-after($pDescriptionText, @oldName),
'div')"/>
<xsl:variable
name="vValue"
select="substring-before(
substring-after($vRough, '>'),
'<')"/>
<xsl:element name="{@newName}">
<xsl:value-of select="normalize-space($vValue)" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Это решение добавляет один дополнительный слой: это позволяет меня хорошо меняют названия полей (через атрибуты oldName
и newName
на каждый элемент <name>
).
Спасибо всем, кто ответил!
имена всегда буквально 'Field1' через' Field5', или же они должны быть полученным из данных? – Borodin
Я бы рекомендовал написать и использовать простую однострочную функцию расширения, которая анализирует свой строковый аргумент как XML и возвращает полученный XmlDocument. Это тривиально с C#. –
@DimitreNovatchev: Это возможно с помощью XSLT, используя ' ' –
Borodin