Количество слов XSLT 1.0 с HTML

Я хочу вызвать шаблон, который урежет поле до 30 слов. Однако это поле содержит HTML, и HTML не должен считаться словом.


person Randy    schedule 20.08.2010    source источник
comment
Вы точно ограничены 1.0?   -  person Daniel Haley    schedule 20.08.2010
comment
Жаль это слышать. fn:tokenize() в версии 2.0 прекрасно работает для подобных вещей. Я уверен, что Димитре, Томалак или Алехандро опубликуют хороший ответ 1.0 (из которого я буду делать заметки). +1 за хороший вопрос   -  person Daniel Haley    schedule 20.08.2010
comment
Мне очень жаль себя. Я так хочу, чтобы наша платформа была 2.0.   -  person Randy    schedule 20.08.2010
comment
@ Рэнди‹ Это очень хороший вопрос, +1. Я приду с решением, но для этой серьезной проблемы мне понадобится некоторое время, так что наберитесь терпения. :)   -  person Dimitre Novatchev    schedule 20.08.2010
comment
Ваш html рассматривается как часть документа xml или как текстовое содержимое в узле xml? то есть у вас есть <field>&lt;i&gt; etc.. в вашем xml или <field><i>etc..?   -  person Flynn1179    schedule 20.08.2010
comment
@Randy: Если ваш содержащийся HTML представляет собой просто текстовое кодирование, такое как пример Flynn1179 (field>&lt;i&gt; вместо <field><i>), тогда вам нужна XSLT-реализация синтаксического анализатора XML (я думаю, мне может потребоваться больше, чем просто какое-то время. Я не знаю о Димитре. ..) или нестандартное решение (возможно, с RTF плюс node-set()). В противном случае решение было бы очень простым.   -  person    schedule 20.08.2010
comment
@Alejandro: Это не так уж плохо, HTML-теги можно оставить нетронутыми, поэтому их не нужно разбирать, просто убедитесь, что всякий раз, когда вы видите , вы копируете все до следующего как есть. Я уже добавил решение, которое делает это.   -  person Flynn1179    schedule 20.08.2010
comment
@Flynn1179: just make sure that whenever you see a &lt;, you copy everything up to the next &gt; as is. Ну, это была бы минимальная реализация парсера!   -  person    schedule 20.08.2010
comment
@Flynn1179: И, кстати, этот синтаксический анализатор сломает <field>&lt;i&gt; This &lt; is less than sign!&lt;i&gt;<field>   -  person    schedule 20.08.2010
comment
Строго говоря, это правда, но только если html все равно недействителен. <i>This < is less than sign</i> недействителен.   -  person Flynn1179    schedule 20.08.2010
comment
@Flynn1179: Я не хочу спорить, но если это закодировано, это будет действительный HTML. Входные данные (не предоставлены) должны быть такими, что <i>This &lt; is less than sign</i> будет кодироваться как &lt;i&gt; This &amp;lt; is less than sign!&lt;i&gt;. Но тогда, возможно, при необходимости вам нужно будет добавить декодирование сущности...   -  person    schedule 20.08.2010
comment
Кстати, в XPath 2.1 WD добавили fn:parse. Кроме того, я проверил свою идею создания RTF с помощью DOE, а затем использовал функцию расширения node-set. я не работала...   -  person    schedule 20.08.2010
comment
@Randy, @Alejandro: я думал, что документ XHTML уже проанализирован. Если бы @randy мог подтвердить, что весь документ просто представлен строкой, тогда НЕТ, это не то, что должен делать XSLT — на самом деле XSLT работает с деревьями как с первичными входными данными, а не со строкой символов, представляющей дерево.   -  person Dimitre Novatchev    schedule 20.08.2010


Ответы (2)


Попробуйте это, хотя, по общему признанию, вызов перевода немного уродлив:

<xsl:template match="field">
  <xsl:value-of select="string-length(translate(normalize-space(.),'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',''))+1" />
</xsl:template>

Это, конечно, требует, чтобы строка в вызове перевода включала все символы, которые могут появиться в поле, кроме пробелов. Он работает, сначала вызывая normalize-space(.), чтобы удалить как двойные пробелы, так и все, кроме текстового содержимого. Затем он удаляет все, кроме пробелов, подсчитывает длину результирующей строки и добавляет единицу. Это означает, что если у вас есть <p>My<b>text</b> test</p>, это будет считаться как 2, так как Mytext будет считаться одним словом.

Если вам нужно более надежное решение, оно немного запутаннее:

<xsl:template match="field">
  <xsl:call-template name="countwords">
    <xsl:with-param name="text" select="normalize-space(.)" />
  </xsl:call-template>
</xsl:template>

<xsl:template name="countwords">
  <xsl:param name="count" select="0" />
  <xsl:param name="text" />
  <xsl:choose>
    <xsl:when test="contains($text,' ')">
      <xsl:call-template name="countwords">
        <xsl:with-param name="count" select="$count + 1" />
        <xsl:with-param name="text" select="substring-after($text,' ')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="$count + 1" /></xsl:otherwise>
  </xsl:choose>
</xsl:template>

Это передает результат normalize-space(.) в рекурсивный именованный шаблон, который вызывает сам себя, когда в $text есть пробел, увеличивает его параметр count и обрезает первое слово каждый раз, используя вызов substring-after($text,' '). Если пробела нет, то $text обрабатывается как одно слово и просто возвращается $count + 1 (+1 для текущего слова).

Имейте в виду, что это будет включать ВСЕ текстовое содержимое в поле, включая содержимое внутренних элементов.

РЕДАКТИРОВАТЬ: Примечание для себя: прочитайте вопрос правильно, просто заметили, что вам нужно больше, чем просто количество слов. Это значительно сложнее сделать, если вы хотите включить какие-либо теги xml, но небольшая модификация вышеизложенного — это все, что нужно, чтобы выплюнуть каждое слово, а не просто подсчитать их:

<xsl:template name="countwords">
  <xsl:param name="count" select="0" />
  <xsl:param name="text" />
  <xsl:choose>
    <xsl:when test="$count = 30" />
    <xsl:when test="contains($text,' ')">
      <xsl:if test="$count != 0"><xsl:text>&#32;</xsl:text></xsl:if>
      <xsl:value-of select="substring-before($text,' ')" />
      <xsl:call-template name="countwords">
        <xsl:with-param name="count" select="$count + 1" />
        <xsl:with-param name="text" select="substring-after($text,' ')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="$text" /></xsl:otherwise>
  </xsl:choose>
</xsl:template>

Есть дополнительное предложение <xsl:when, чтобы просто остановить рекурсию, когда count достигает 30, а предложение recursive выводит текст после добавления пробела в начале, если это не было первое слово.

РЕДАКТИРОВАТЬ: Хорошо, вот решение, которое сохраняет экранированное содержимое XML:

<xsl:template match="field">
  <xsl:call-template name="countwords">
    <xsl:with-param name="text" select="." />
  </xsl:call-template>
</xsl:template>

<xsl:template name="countwords">
  <xsl:param name="count" select="0" />
  <xsl:param name="text" />
  <xsl:choose>
    <xsl:when test="starts-with($text, '&lt;')">
      <xsl:value-of select="concat(substring-before($text,'&gt;'),'&gt;')" />
      <xsl:call-template name="countwords">
        <xsl:with-param name="count">
          <xsl:choose>
            <xsl:when test="starts-with(substring-after($text,'&gt;'),' ')"><xsl:value-of select="$count + 1" /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$count" /></xsl:otherwise>
          </xsl:choose>
        </xsl:with-param>
        <xsl:with-param name="text" select="substring-after($text,'&gt;')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="(contains($text, '&lt;') and contains($text, ' ') and string-length(substring-before($text,' ')) &lt; string-length(substring-before($text,'&lt;'))) or (contains($text,' ') and not(contains($text,'&lt;')))">
      <xsl:choose>
        <xsl:when test="$count &lt; 29"><xsl:value-of select="concat(substring-before($text, ' '),'&#32;')" /></xsl:when>
        <xsl:when test="$count = 29"><xsl:value-of select="substring-before($text, ' ')" /></xsl:when>
      </xsl:choose>
      <xsl:call-template name="countwords">
        <xsl:with-param name="count">
          <xsl:choose>
            <xsl:when test="normalize-space(substring-before($text, ' ')) = ''"><xsl:value-of select="$count" /></xsl:when>
            <xsl:otherwise><xsl:value-of select="$count + 1" /></xsl:otherwise>
          </xsl:choose>
        </xsl:with-param>
        <xsl:with-param name="text" select="substring-after($text,' ')" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="(contains($text, '&lt;') and contains($text, ' ') and string-length(substring-before($text,' ')) &gt; string-length(substring-before($text,'&lt;'))) or contains($text,'&lt;')">
      <xsl:if test="$count &lt; 30">
        <xsl:value-of select="substring-before($text, '&lt;')" />
      </xsl:if>
      <xsl:call-template name="countwords">
        <xsl:with-param name="count" select="$count" />
        <xsl:with-param name="text" select="concat('&lt;',substring-after($text,'&lt;'))" />
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:if test="$count &lt; 30">
        <xsl:value-of select="$text" />
      </xsl:if>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Если вам нужно что-то из этого объяснить лучше, дайте мне знать, я бы не хотел вдаваться в подробности, если вам это не нужно!

person Flynn1179    schedule 20.08.2010
comment
Основная проблема с первым предложенным решением заключается в том, что оно будет распознавать только слова в латинском алфавите и не слова, такие как 3X2. Основная проблема со вторым предложенным решением заключается в том, что оно будет считать такие строки, как: слово, слово, слово: слово, слово. слово, слово; слово, ... и т. д. как отдельные слова. - person Dimitre Novatchev; 20.08.2010
comment
Другая проблема заключается в том, что такие строки, как ---------------, _______________, =========== и т. д., будут считаться словами. - person Dimitre Novatchev; 20.08.2010
comment
Я считаю, что совершенно ясно выразил это мнение, когда сказал: «Это, конечно, требует, чтобы строка в вызове перевода включала все символы, которые могут появиться в поле». - person Flynn1179; 20.08.2010
comment
К сожалению, эти решения не учитывают пробелы в HTML: ‹i style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: курсив; font-size:11px;›От того, как мы адаптируемся к изменениям, зависит наш успех‹/i›‹br /› ‹span style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: нормальный; font-size:11px;›Упростите вопросы возмещения расходов с помощью ресурса, который поможет пользователям выбрать правильный план для них. Нажмите здесь, чтобы узнать больше информации и подписаться на обновления‹/span› - person Randy; 20.08.2010
comment
Что касается подсчета таких вещей, как слово, слово как двух отдельных слов, у вас есть два варианта: либо заменить normalize-space(.) на normalize-space(translate(., ',:;.', ' ')), либо добавить в шаблон отдельное предложение <xsl:when> для каждого разделителя. Однако, если честно, если вам нужен такой уровень синтаксического анализа, я бы не рекомендовал делать это с помощью xpath/xslt. - person Flynn1179; 20.08.2010
comment
@Randy: это решение полностью игнорирует разметку html; разве это не то, что вы имели в виду, когда сказали, что HTML не должен считаться словом? В какой момент в приведенном выше примере вы ожидаете усечения решения? - person Flynn1179; 20.08.2010
comment
‹i стиль = семейство шрифтов: Arial, Helvetica, без засечек; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: курсив; font-size:11px;›От того, как мы адаптируемся к изменениям, зависит наш успех‹/i›‹br /› ‹span style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: обычный; font-size:11px;›Упростите вопросы возмещения расходов с помощью ресурса, который поможет пользователям выбрать правильный план для них. кликните сюда - person Randy; 20.08.2010
comment
Было бы здорово, если бы он также мог включать любые закрывающие теги html, поэтому в приведенном выше примере - ‹i style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: курсив; font-size:11px;›От того, как мы адаптируемся к изменениям, зависит наш успех‹/i›‹br /› ‹span style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: нормальный; font-size:11px;›Упростите вопросы возмещения расходов с помощью ресурса, который поможет пользователям выбрать правильный план для них. Нажмите здесь‹/span› - person Randy; 20.08.2010
comment
Ах, хорошо… мое решение просто даст вам «Как мы адаптируемся к изменениям, определяющим наш успех. Упростите вопросы возмещения с помощью ресурса, который поможет пользователям выбрать правильный план для них». Щелкните здесь' в качестве вывода. Можно адаптировать этот шаблон, чтобы «пропустить» html-материал, но мне нужно знать, экранируется ли ваш html как текстовое содержимое элемента xml или нет. - person Flynn1179; 20.08.2010
comment
нет, это не экранировано, я стиль = семейство шрифтов: Arial, Helvetica, без засечек; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: курсив; font-size:11px;Как мы адаптируемся к изменениям, будет определять наш успех/ibr / span style=font-family:Arial, Helvetica, sans-serif; заполнение сверху: 2px; вес шрифта: нормальный; стиль шрифта: нормальный; font-size:11px;Упростите вопросы возмещения расходов с помощью ресурса, который поможет пользователям выбрать правильный план для них. Нажмите здесь, чтобы узнать больше информации и подписаться на обновления/промежуток - person Randy; 20.08.2010
comment
@Flynn1179: Вы просто постепенно понимаете, насколько вы далеки даже от полного понимания проблемы, которую определил Рэнди - вы все еще не осознали некоторые существенные проблемы - вы все еще не видите их вообще. Было бы неплохо рассмотреть возможность удаления вашего ответа. - person Dimitre Novatchev; 20.08.2010
comment
Поскольку это было, возможно, это не было адекватным решением, но это не достаточная причина для его удаления; возможность подсчета слов может быть полезна другим читателям. Однако теперь, когда вопрос немного прояснился, я добавил решение, которое, похоже, работает так, как предполагалось. - person Flynn1179; 20.08.2010
comment
@Flynn1179: ваше решение очень далеко от того, о чем просили. С этим XML-документом: <field> <html> <p>This</p><b>i</b>s it. </html> </field> он выдает: `Это оно.`, но желаемый результат (для ограничения в два слова): <field> <html> <p>This</p><b>i</b>s </html> </field> Таким образом, есть по крайней мере две проблемы: 1. Неправильный подсчет слов; 2. Потеря разметки. Итак, еще раз, ваш ответ до сих пор не то, что имелось в виду и хотел - пожалуйста, рассмотрите возможность предоставления соответствующего ответа или удаления этого. - person Dimitre Novatchev; 20.08.2010
comment
@Dimitre: Это не то, о чем просили. Прочтите комментарии еще раз, html кодируется как текст с помощью , это не элементы xml. - person Flynn1179; 20.08.2010
comment
NP, но, честно говоря, я все еще думаю, что xslt/xpath - не лучший способ сделать это, он действительно не предназначен для такой обработки текста, если, конечно, это не часть более крупного процесса. - person Flynn1179; 20.08.2010
comment
@Flynn1179: Теперь, когда я вижу здесь комментарии Дмитрия, я действительно думаю, что для этого потребуется какой-то синтаксический анализатор, потому что имя псевдоэлемента повлияет на результаты. В противном случае следует указать на все эти предположения: начальные и конечные теги и ссылки на объекты (в частности, &lt;) закодированы, элементы блочного стиля имеют явное пространство (внутри или следующего элемента) между ними и следующим словом. Кроме того, если он должен сохранить кодировку HTML, это может быть недействительным. - person ; 20.08.2010
comment
@Flynn1179: Даже если проблема определена так, как вы говорите ???, ваш код все равно дает неправильные результаты. С этим вводом: <field>&lt;p&gt;Monday&lt;/p&gt;&lt;b&gt;T&lt;/b&gt;uesday</field> результат будет следующим: &lt;p&gt;Monday&lt;/p&gt;&lt;b&gt;T&lt;/b&gt;uesday Думаю, правильный результат должен быть: &lt;p&gt;Monday pMonday/pbT/buesday - person Dimitre Novatchev; 20.08.2010
comment
@Randy: я полностью согласен с @Flynn1179 в том, что синтаксический анализ неразобранного HTML не подходит для XSLT (это можно сделать, но я и никто в здравом уме не стал бы делать это с XSLT). - person Dimitre Novatchev; 20.08.2010
comment
Я полностью согласен, что это, безусловно, идеальное решение, но оно, безусловно, дает желаемый результат из заданного ввода. @Dimitre, в этом примере он делает именно так, как должен: оставляя ввод без изменений, если в нем меньше 30 слов. Да, в некоторых случаях это, вероятно, будет неправильный подсчет слов, но более «идеальное» решение, вероятно, будет излишним для этой цели. @Randy хотел только обрезать поле до 30 слов, а не вставлять лишние пробелы там, где слова разделены только разметкой. - person Flynn1179; 20.08.2010
comment
Между прочим, у этого решения есть один довольно важный недостаток (я удивлен, что никто больше его не заметил): оно оставляет всю оставшуюся разметку после 30 слов, а не только закрывающие теги. Например, если у вас есть ‹b›что-то‹/b› в качестве 40-го слова или около того, вы получите ‹b›‹/b› на выходе; не уверен, насколько это проблема, но текст все равно должен быть правильным. - person Flynn1179; 20.08.2010
comment
Я хотел бы иметь возможность использовать что-то помимо XSLT. Однако наша CMS основана на использовании XML и XSL. К счастью, код должен быть довольно чистым. Большое спасибо за Вашу помощь. - person Randy; 20.08.2010
comment
@Flynn1179: Нет, я отредактировал ваш код, чтобы получить только одно слово. Он по-прежнему выводит два слова, не понимая, что на самом деле это два слова, а не одно. - person Dimitre Novatchev; 20.08.2010
comment
@Randy: Если вы должны начать с непроанализированного текста XHTML, я бы сначала удалил его и проанализировал, а затем применил бы XSLT к этому проанализированному XML-документу. Любое другое решение с XSLT слишком дорого и сложно в обслуживании — может быть даже трудно доказать, что такое решение действительно работает. - person Dimitre Novatchev; 20.08.2010
comment
@Flynn1179 - похоже, проблема с пустыми тегами. У вас есть идея, как решить эту проблему? - person Randy; 23.08.2010
comment
Нереально, если не делать что-то ужасно сложное. Если вы можете жить с незакрытыми тегами, если они все еще открыты после 30 слов, довольно легко просто прекратить вывод НИЧЕГО в этот момент, но в противном случае в значительной степени потребуется иметь возможность анализировать XML. Я настоятельно рекомендую использовать для этого что-то другое, кроме XSLT, или рассмотреть вопрос о добавлении функции расширения, которую может использовать ваш xslt, например для C#: csharpfriends.com/Articles/getArticle.aspx?articleID=64 - person Flynn1179; 24.08.2010

Здесь немного другой подход:

Если вы можете очистить свой ввод, чтобы получить нормализованную строку текста, который вы хотите подсчитать, вы можете сравнить длину строки строки с пробелами с длиной строки строки с удаленными пробелами. Разница должна заключаться в количестве слов.

Функция подсчета слов (шаблон) будет выглядеть примерно так:

<xsl:template name="wordCount">
    <xsl:param name="input" required="yes"/>
    <xsl:param name="sep" select="'‒–—―'"/>
    <xsl:variable name="big"><xsl:value-of select="normalize-space(translate($input, $sep, ' '))"/></xsl:variable>
    <xsl:variable name="small"><xsl:value-of select="translate($big, ' ', '')"/></xsl:variable>
    <xsl:value-of select="string-length($big)-string-length($small)"/>
</xsl:template>

Параметр $sep позволяет вам определить список любых символов (а также пробелов), которые вы хотите считать разделителями слов.

Затем вы можете использовать конструктор последовательности при вызове шаблона для создания нужной строки (я оставлю это в качестве упражнения для читателя):

<xsl:call-template name="wordCount">
    <xsl:with-param name="input">
        <!-- templates etc to output text from html -->
    </xsl:with-param>
</xsl:call-template>
person Tom Hillman    schedule 07.02.2012