2013-05-06 13 views
2

Входной I Have:Оптимизация tokenizing XSLT

Я работаю со списком Sharepoint, который производит RSS-каналы в следующем виде:

<?xml version="1.0"?> 
<rss> 
    <channel> 
    <!-- Irrelevant Fields --> 
    <item> 
     <title type="text">Title</title> 
     <description type="html"> 
     &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field2:&lt;/b&gt; Value 2&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt; 
     </description> 
    </item> 
    <item> 
     <title type="text">Title</title> 
     <description type="html"> 
     &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt; 
     </description> 
    </item> 
    <item> 
     <title type="text">Title</title> 
     <description type="html"> 
     &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field2:&lt;/b&gt; Value 2&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt; 
     &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt; 
     </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> 

Правила (обновлено):

  1. Это должно быть XSLT 1.0 решение.
  2. xxx:node-set - единственная действительная функция расширения, доступная мне; это включает функции расширения, написанные на других языках, такие как C# или Javascript.
  3. Если какая-либо информация о поле отсутствует, должен быть выведен пустой элемент. Обратите внимание, что в моем желаемом выводе пустой <Field2> дочерний элемент во втором элементе <Event>.
  4. Мы не можем предположить, что сами имена полей будут следовать любому конкретному шаблону; они так же могут быть <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="'&#10;'"/> 
     </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="'&#10;'"/> 
     </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(), ':'), 
       '&lt;div&gt;&lt;b&gt;')}"> 
     <xsl:value-of 
     select="substring-before(
        substring-after(., ':&lt;/b&gt; '), 
        '&lt;/div&gt;')"/> 
    </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> 

Есть два основных проблем с моим раствором:

  1. Он чувствует себя неуклюжим; есть повторяющийся код, и это кажется немного громоздким. Я думаю, что может произойти какая-то оптимизация?
  2. Обратите внимание, что он выводит пустые <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, '&gt;'), 
       '&lt;')"/> 
    <xsl:element name="{@newName}"> 
     <xsl:value-of select="normalize-space($vValue)" /> 
    </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 

Это решение добавляет один дополнительный слой: это позволяет меня хорошо меняют названия полей (через атрибуты oldName и newName на каждый элемент <name>).

Спасибо всем, кто ответил!

+0

имена всегда буквально 'Field1' через' Field5', или же они должны быть полученным из данных? – Borodin

+1

Я бы рекомендовал написать и использовать простую однострочную функцию расширения, которая анализирует свой строковый аргумент как XML и возвращает полученный XmlDocument. Это тривиально с C#. –

+0

@DimitreNovatchev: Это возможно с помощью XSLT, используя ' ' – Borodin

ответ

4

Возможно, вас это заинтересует. Я использовал литеральные имена полей Field1, хотя Field5 и, поскольку у вас есть доступ к node-set, я добавил эти имена в переменную, которую можно удобно изменить.

Код обрабатывает текст description, чтобы извлечь значение для каждого имени поля, взяв на нем два укуса. Первый проход создает $rough, выбирая текст после имени поля и перед текстом div. Это даст что-то вроде :&lt;/b&gt; Value 1&lt;/ (или :</b> Value 1</). Следующее уточнение занимает все в $rough после &gt; и до &lt;, что дает Value 1. Пробелы обрезаются из этого окончательного значения, используя normalize-space в элементе xsl:value-of.

XSLT сам по себе заботится о отсутствующем Field2 (или любом поле), возвращая нулевую строку из substring-before, если строка разделителя не найдена в целевой строке.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:ext="http://exslt.org/common" 
    exclude-result-prefixes="ext" 
    version="1.0"> 

    <xsl:strip-space elements="*"/> 
    <xsl:output method="xml" indent="yes"/> 

    <xsl:variable name="names"> 
     <name>Field1</name> 
     <name>Field2</name> 
     <name>Field3</name> 
     <name>Field4</name> 
     <name>Field5</name> 
    </xsl:variable> 

    <xsl:template match="/"> 
     <Events> 
      <xsl:apply-templates select="rss/channel/item"/> 
     </Events> 
    </xsl:template> 

    <xsl:template match="item"> 
     <xsl:variable name="description" select="description"/> 
     <Event> 
      <Category> 
       <xsl:value-of select="title"/> 
      </Category> 
      <xsl:for-each select="ext:node-set($names)/name"> 
       <xsl:call-template name="extract"> 
        <xsl:with-param name="text" select="$description"/> 
        <xsl:with-param name="field-name" select="."/> 
       </xsl:call-template> 
       <xsl:variable name="field-name" select="."/> 
      </xsl:for-each> 
     </Event> 
    </xsl:template> 

    <xsl:template name="extract"> 
     <xsl:param name="text"/> 
     <xsl:param name="field-name"/> 
     <xsl:variable name="rough" select="substring-before(substring-after($text, $field-name), 'div')"/> 
     <xsl:variable name="value" select="substring-before(substring-after($rough, '&gt;'), '&lt;')"/> 
     <xsl:element name="{$field-name}"> 
      <xsl:value-of select="normalize-space($value)"/> 
     </xsl:element> 
    </xsl:template> 

</xsl:stylesheet> 

выход

<?xml version="1.0" encoding="utf-8"?> 
<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> 
+0

Спасибо, @Borodin. Я смог использовать вашу идею в качестве основы для решения. Ценю вашу помощь. – ABach

0

Ее рекурсивны решение, основанное на очень хорошем "экстракт" форма шаблон @Borodin. С небольшими преимуществами это также будет работать без node-set().

<?xml version="1.0"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> 
    <xsl:output method="xml" indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <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:call-template name="Field" > 
       <xsl:with-param name="fnr" select="'1'" /> 
       <xsl:with-param name="max_fnr" select="'5'" /> 
      </xsl:call-template> 
     </Event> 
    </xsl:template> 

    <xsl:template name="Field"> 
     <xsl:param name="fnr" /> 
     <xsl:param name="max_fnr" /> 

     <xsl:call-template name="extract"> 
      <xsl:with-param name="text" select="."/> 
      <xsl:with-param name="field-name" select="concat('Field',$fnr)"/> 
     </xsl:call-template> 

     <xsl:if test="$fnr &lt; $max_fnr"> 
      <xsl:call-template name="Field" > 
       <xsl:with-param name="fnr" select="$fnr+1" /> 
       <xsl:with-param name="max_fnr" select="$max_fnr" /> 
      </xsl:call-template> 
     </xsl:if> 
    </xsl:template> 

    <xsl:template name="extract"> 
     <xsl:param name="text"/> 
     <xsl:param name="field-name"/> 
     <xsl:variable name="rough" select="substring-before(substring-after($text, $field-name), 'div')"/> 
     <xsl:variable name="value" select="substring-before(substring-after($rough, '&gt;'), '&lt;')"/> 
     <xsl:element name="{$field-name}"> 
      <xsl:value-of select="normalize-space($value)"/> 
     </xsl:element> 
    </xsl:template> 
</xsl:stylesheet> 

Какой будет генерировать следующий вывод:

<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> 
+0

Благодарим вас за решение, @ hr_117. К сожалению, я не могу предположить, что поля будут называться с помощью шаблона (например, «Поле», за которым следует числовое приращение). Я понимаю, что это вводит в заблуждение в первоначальном вопросе; мои извинения. – ABach

0

Здесь решение с некоторыми "если".
Если содержание описания всегда является «правильно сформированным XML» (как и в вашем примере) и
, если вы можете сделать два отдельных прохода (два вызова процессора xslt).

Пасс 1: Создайте временный файл xml с disable-output-escaping="yes" за содержание описания (легко и понятно).

<?xml version="1.0"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> 
    <xsl:output method="xml" indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="description"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:value-of select="." disable-output-escaping="yes"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 

Pass 2: Сформировать ожидаемый выход из временного файла XML (также легко сейчас):

<?xml version="1.0"?> 
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0"> 
    <xsl:output method="xml" indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <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:call-template name="Field" > 
       <xsl:with-param name="fnr" select="'1'" /> 
       <xsl:with-param name="max_fnr" select="'5'" /> 
      </xsl:call-template> 
     </Event> 
    </xsl:template> 

    <xsl:template name="Field"> 
     <xsl:param name="fnr" /> 
     <xsl:param name="max_fnr" /> 
     <xsl:element name="Field{$fnr}" > 
      <xsl:value-of select="description/div[b[text()=concat('Field', $fnr, ':')]]/text()"/> 
     </xsl:element> 
     <xsl:if test="$fnr &lt; $max_fnr"> 
      <xsl:call-template name="Field" > 
       <xsl:with-param name="fnr" select="$fnr+1" /> 
       <xsl:with-param name="max_fnr" select="$max_fnr" /> 
      </xsl:call-template> 
     </xsl:if> 
    </xsl:template> 
</xsl:stylesheet>