Вероятно, вам было бы лучше сделать это программно, а не с помощью чистого XSLT, но если вам нужно использовать XSLT, вот один из способов сделать это. Он включает несколько таблиц стилей, хотя, если бы вы могли использовать функции расширения, вы можете использовать наборы узлов и объединить их в одну большую (и неприятную) таблицу стилей.
Первая таблица стилей будет копировать исходный XML, но «маркировать» любой текст, который он найдет, так что каждое слово в тексте станет отдельным элементом «WORD».
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Copy existing nodes and attributes -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Match text nodes -->
<xsl:template match="text()">
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</xsl:template>
<!-- Splits a string into separate elements for each word -->
<xsl:template name="tokenize">
<xsl:param name="string"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="$delimiter and contains($string, $delimiter)">
<xsl:variable name="word" select="normalize-space(substring-before($string, $delimiter))"/>
<xsl:if test="string-length($word) > 0">
<WORD>
<xsl:value-of select="$word"/>
</WORD>
</xsl:if>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="substring-after($string, $delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="word" select="normalize-space($string)"/>
<xsl:if test="string-length($word) > 0">
<WORD>
<xsl:value-of select="$word"/>
</WORD>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Шаблон XSLT, используемый для «токенизации» строки текста, я взял из этого вопроса здесь:
tokenizing-сортировочный-с-1-0 XSLT
(обратите внимание, что в XSLT2.0, я полагаю, есть функция токенизации, которая упростит вышеперечисленное)
Это даст вам XML как ...
<PUBLDES>
<WORD>The</WORD>
<IT>
<WORD>European</WORD>
<WORD>Journal</WORD>
<WORD>of</WORD>
....
И так далее ...
Далее, это случай обхода этого XML-документа с использованием другого XSLT-документа с выводом только до первых 45 словарных элементов. Чтобы сделать это, я неоднократно применяю шаблон, сохраняя промежуточную сумму количества найденных слов в настоящее время. При сопоставлении узла есть три возможности
- Соответствует элементу WORD : Вывести его. Продолжайте обработку от следующего родного брата, если общее количество не достигнуто.
- Соответствует элементу, где количество слов ниже него меньше, чем общее число : Скопируйте весь элемент, а затем продолжите обработку следующего брата, если общее количество не достигнуто
- Соответствие элементам, в которых количество слов ниже будет превышать общее : скопировать текущий узел (но не его дочерние элементы) и продолжить обработку с первого дочернего элемента.
Вот таблица стилей, во всей ее отвратительности
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="WORDCOUNT">6</xsl:variable>
<!-- Match root element -->
<xsl:template match="/">
<xsl:apply-templates select="descendant::*[1]" mode="word">
<xsl:with-param name="previousWords">0</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<!-- Match any node -->
<xsl:template match="node()" mode="word">
<xsl:param name="previousWords"/>
<!-- Number of words below the element (at any depth) -->
<xsl:variable name="childWords" select="count(descendant::WORD)"/>
<xsl:choose>
<!-- Matching a WORD element -->
<xsl:when test="local-name(.) = 'WORD'">
<!-- Copy the word -->
<WORD>
<xsl:value-of select="."/>
</WORD>
<!-- If there are still words to output, continue processing at next sibling -->
<xsl:if test="$previousWords + 1 < $WORDCOUNT">
<xsl:apply-templates select="following-sibling::*[1]" mode="word">
<xsl:with-param name="previousWords">
<xsl:value-of select="$previousWords + 1"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:when>
<!-- Match a node where the number of words below it is within allowed limit -->
<xsl:when test="$childWords <= $WORDCOUNT - $previousWords">
<!-- Copy the element -->
<xsl:copy>
<!-- Copy all its desecendants -->
<xsl:copy-of select="*|@*"/>
</xsl:copy>
<!-- If there are still words to output, continue processing at next sibling -->
<xsl:if test="$previousWords + $childWords < $WORDCOUNT">
<xsl:apply-templates select="following-sibling::*[1]" mode="word">
<xsl:with-param name="previousWords">
<xsl:value-of select="$previousWords + $childWords"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:when>
<!-- Match nodes where the number of words below it would exceed current limit -->
<xsl:otherwise>
<!-- Copy the node -->
<xsl:copy>
<!-- Continue processing at very first child node -->
<xsl:apply-templates select="descendant::*[1]" mode="word">
<xsl:with-param name="previousWords">
<xsl:value-of select="$previousWords"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Если вы выводите только первые 4 слова, скажем, это даст вам следующий вывод
<PUBLDES>
<WORD>The</WORD>
<IT>
<WORD>European</WORD>
<WORD>Journal</WORD>
<WORD>of</WORD>
</IT>
</PUBLDES>
Конечно, тогда вам понадобится еще одно преобразование, чтобы удалить элементы WORD и просто оставить текст. Это должно быть довольно просто ....
Это все очень неприятно, но это лучшее, что я мог придумать на данный момент!