Когда тест висит в бесконечном цикле - PullRequest
2 голосов
/ 16 ноября 2009

Я токенизирую строку с помощью XSLT 1.0 и пытаюсь предотвратить распознавание пустых строк как токенов. Вот вся функция, основанная на XSLT Cookbook :

<xsl:template name="tokenize">
    <xsl:param name="string" select="''" />
    <xsl:param name="delimiters" select="';#'" />
    <xsl:param name="tokensplitter" select="','" />
    <xsl:choose>
        <!-- Nothing to do if empty string -->
        <xsl:when test="not($string)" />

        <!-- No delimiters signals character level tokenization -->
        <xsl:when test="not($delimiters)">
            <xsl:call-template name="_tokenize-characters">
                <xsl:with-param name="string" select="$string" />
                <xsl:with-param name="tokensplitter" select="$tokensplitter" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="_tokenize-delimiters">
                <xsl:with-param name="string" select="$string" />
                <xsl:with-param name="delimiters" select="$delimiters" />
                <xsl:with-param name="tokensplitter" select="$tokensplitter" />
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template name="_tokenize-characters">
    <xsl:param name="string" />
    <xsl:param name="tokensplitter" />
    <xsl:if test="$string">
        <token><xsl:value-of select="substring($string, 1, 1)"/></token>
        <xsl:call-template name="_tokenize-characters">
            <xsl:with-param name="string" select="substring($string, 2)" />
        </xsl:call-template>
    </xsl:if>
</xsl:template>

<xsl:template name="_tokenize-delimiters">
    <xsl:param name="string" />
    <xsl:param name="delimiters" />
    <xsl:param name="tokensplitter" />

    <!-- Extract a delimiter -->
    <xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/>
    <xsl:choose>
        <!-- If the delimiter is empty we have a token -->
        <xsl:when test="not($delimiter) and $string != ''">
            <xsl:text>£</xsl:text>
            <token><xsl:value-of select="$string"/></token>
            <xsl:text>$</xsl:text>
            <xsl:value-of select="$tokensplitter"/>
        </xsl:when>
        <!-- If the string contains at least one delimiter we must split it -->
        <xsl:when test="contains($string, $delimiter)">
            <!-- If it starts with the delimiter we don't need to handle the before part -->
            <xsl:if test="not(starts-with($string, $delimiter))">
                <!-- Handle the part that comes before the current delimiter with the next delimiter. -->
                <!-- If there is no next the first test in this template will detect the token. -->
                <xsl:call-template name="_tokenize-delimiters">
                    <xsl:with-param name="string" select="substring-before($string, $delimiter)" />
                    <xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
                    <xsl:with-param name="tokensplitter" select="$tokensplitter" />
                </xsl:call-template>
            </xsl:if>
            <!-- Handle the part that comes after the delimiter using the current delimiter -->
            <xsl:call-template name="_tokenize-delimiters">
                <xsl:with-param name="string" select="substring-after($string, $delimiter)" />
                <xsl:with-param name="delimiters" select="$delimiters" />
                <xsl:with-param name="tokensplitter" select="$tokensplitter" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <!-- No occurrences of current delimiter so move on to next -->
            <xsl:call-template name="_tokenize-delimiters">
                <xsl:with-param name="string" select="$string" />
                <xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
                <xsl:with-param name="tokensplitter" select="$tokensplitter" />
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Значение для string, которое я передаю:

Европа; # 6; # Глобальный; # 3; # Средний Восток, Африка и Кавказ; 2; #Europe; # 6; #global; # 3; #Middle Восток, Африка и Кавказ

(Индикаторы £ и $ просто есть, поэтому я вижу, что пустые строки не выводятся. Это в SharePoint, поэтому их сложно отладить.)

Этот код зависает при обработке XSLT. Строка, вызывающая проблему, <xsl:when test="not($delimiter) and $string != ''">. Как только я удаляю второй and тест, он снова работает. Я также безуспешно пытался and string($string).

Кто-нибудь знает, почему это происходит и как его решить?

Ответы [ 2 ]

4 голосов
/ 16 ноября 2009

Я полагаю, что мое подозрение было верным: вы переходите к своему предложению <xsl:otherwise>, когда $string имеет значение, а $delimiter нет, вызывая бесконечный цикл, как вы говорите.

Добавьте следующее новое предложение <xsl:when> после первого:

    <xsl:when test="not($delimiter) and $string = ''" />

Это не позволит исполнению войти в блок <xsl:otherwise>, когда это не должно быть.


Более подробное объяснение того, что происходит и почему это происходит:

В блоке <xsl:choose> есть три ветви.

    <xsl:when test="not($delimiter) and $string != ''">
    <xsl:when test="contains($string, $delimiter)">
    <xsl:otherwise>

Таким образом, когда ни $string, ни $delimiter не содержат значений, первое условие не выполняется (поскольку $string != '' имеет значение false). Второе условие выполняется (потому что contains(nil,nil) всегда возвращает true (подтверждено в Visual Studio)), что снова вызывает шаблон с теми же параметрами (потому что substring-before возвращает пустую строку, так как он не содержит пустой разделитель). Ergo, бесконечный цикл.

Исправлено добавление нового пустого условия:

    <xsl:when test="not($delimiter) and $string != ''">
    <xsl:when test="not($delimiter) and $string = ''" />
    <xsl:when test="contains($string, $delimiter)">
    <xsl:otherwise>

РЕДАКТИРОВАТЬ: я возился и не могу найти ссылку на определенное поведение contains, когда второй параметр пуст или ноль. Тесты показали, что движок Microsoft Visual Studio XSLT возвращает true, когда второй параметр пуст или равен нулю. Я не уверен, является ли это определенным поведением или это должен решать разработчик. У кого-нибудь есть окончательный ответ на этот вопрос? Томалак, я смотрю на тебя.

0 голосов
/ 16 ноября 2009

Не является ли string зарезервированным словом? Можете ли вы заменить это имя на что-нибудь еще?

РЕДАКТИРОВАНИЕ: Поставляемый код без проблем запускается здесь: XSLT Tryit Editor v1.0 с использованием:

<xsl:call-template name="tokenize">
   <xsl:with-param name="string">Europe;#6;#Global...</xsl:with-param>
</xsl:call-template>
...