2010-10-29 7 views
3

Простая ситуация ... С любым случайным XML-файлом я хочу создать список каждого содержащегося в нем узла, но без каких-либо дубликатов! Так что-то вроде:Список всех узлов в файле XML

<root name="example"> 
    <child id="1"> 
    <grandchild/> 
    </child> 
    <child id="2"/> 
    <child id="3"/> 
</root> 

переведен на:

/root 
/root/@name 
/root/child 
/root/child/@id 
/root/child/grandchild 

Как это сделать, только с помощью XSLT?

+1

Хороший вопрос, +1. См. Мой ответ для полного, не слишком долгого и эффективного решения XSLT 1.0. :) –

+0

Смотрите мой комментарий и мой собственный код в собственном ответе. Но ваше решение отличное. Но я не хочу нумерации, просто имена. –

ответ

2

Просто для удовольствия, без функции расширения.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="text()"/> 
    <xsl:template match="*|@*"> 
     <xsl:param name="pPath"/> 
     <xsl:param name="pNames" select="'&#xA;'"/> 
     <xsl:variable name="vPath" 
         select="concat($pPath,'/', 
            substring('@', 
               1 div (count(.|../@*) = 
                 count(../@*))), 
            name())"/> 
     <xsl:variable name="vNames"> 
      <xsl:if test="not(contains($pNames, 
             concat('&#xA;',$vPath,'&#xA;')))"> 
       <xsl:value-of select="concat($vPath,'&#xA;')"/> 
      </xsl:if> 
      <xsl:apply-templates select="*[1]|@*"> 
       <xsl:with-param name="pPath" select="$vPath"/> 
       <xsl:with-param name="pNames" select="$pNames"/> 
      </xsl:apply-templates> 
     </xsl:variable> 
     <xsl:value-of select="$vNames"/> 
     <xsl:apply-templates select="following-sibling::*[1]"> 
      <xsl:with-param name="pPath" select="$pPath"/> 
      <xsl:with-param name="pNames" select="concat($pNames,$vNames)"/> 
     </xsl:apply-templates> 
    </xsl:template> 
</xsl:stylesheet> 

Выход:

/root 
/root/@name 
/root/child 
/root/child/@id 
/root/child/grandchild 

Edit: Лучше пример XSLT/XPath 2.0. Эта линия XPath 2.0:

string-join(
    distinct-values(
     (//*|//@*) 
     /string-join(
      (ancestor::node()/name(), 
      if (self::attribute()) 
       then concat('@',name()) 
       else name()), 
      '/')), 
    '&#xA;') 
+0

Принял это просто потому, что это кратчайшее решение. :-) Версия XPath 2.0 тоже выглядит интересной, но, к сожалению, я связан с 1.0 ... –

+0

@Workshop Alex: Я рад, что это было полезно. На сегодняшний день нет ограничений на использование XSLT 2.0: есть версии .NET, Java, .COM, бесплатные и оплачиваемые. – 2010-11-01 12:26:53

+0

Да, я знаю. К сожалению, я буду называть таблицу стилей из приложения Delphi/WIN32, используя MSXML. Эта таблица стилей будет действительно полезной при определении того, что другое приложение Delphi/WIN32 отправляет внутри и снаружи. Я просто не хочу добавлять зависимость к этому, кроме тех, которые он уже использует. –

2

Это преобразование (133 строк, и многие из них закомментирована):

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
exclude-result-prefixes="msxsl"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:key name="kPathByVal" match="path" use="."/> 

<xsl:template match="node()|@*"> 
    <path> 
    <xsl:call-template name="buildPath"/> 
    </path> 
    <xsl:apply-templates select="node()|@*"/> 
</xsl:template> 

<xsl:template match="/"> 
    <xsl:variable name="vrtfPaths"> 
    <xsl:apply-templates/> 
    </xsl:variable> 

    <xsl:variable name="vPaths" select="msxsl:node-set($vrtfPaths)/*"/> 

    <xsl:for-each select= 
    "$vPaths[generate-id() 
      = 
      generate-id(key('kPathByVal',.)[1]) 
      ] 
    "> 
    <xsl:value-of select="concat(.,'&#xA;')"/> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="buildPath"> 
<xsl:variable name="pNode" select="."/> 
    <xsl:variable name="theResult"> 
    <xsl:for-each select="$pNode"> 
    <xsl:variable name="theNode" select="."/> 
    <xsl:for-each select= 
    "$theNode 
    | 
    $theNode/ancestor-or-self::node()[..]"> 
     <xsl:element name="slash">/</xsl:element> 
     <xsl:choose> 
     <xsl:when test="self::*"> 
      <xsl:element name="nodeName"> 
      <xsl:value-of select="name()"/> 
      </xsl:element> 
     </xsl:when> 
     <xsl:otherwise> <!-- This node is not an element --> 
      <xsl:choose> 
      <xsl:when test="count(. | ../@*) = count(../@*)"> 
      <!-- Attribute --> 
       <xsl:element name="nodeName"> 
       <xsl:value-of select="concat('@',name())"/> 
       </xsl:element> 
      </xsl:when> 
      <xsl:when test="self::text()"> <!-- Text --> 
       <xsl:element name="nodeName"> 
       <xsl:value-of select="'text()'"/> 
       <xsl:variable name="thisPosition" 
          select="count(preceding-sibling::text())"/> 
       <xsl:variable name="numFollowing" 
          select="count(following-sibling::text())"/> 
       <xsl:if test="$thisPosition + $numFollowing > 0"> 
        <xsl:value-of select= 
        "concat('[', $thisPosition +1, ']')"/> 
       </xsl:if> 
       </xsl:element> 
      </xsl:when> 
      <xsl:when test="self::processing-instruction()"> 
      <!-- Processing Instruction --> 
       <xsl:element name="nodeName"> 
       <xsl:value-of select="'processing-instruction()'"/> 
       <xsl:variable name="thisPosition" 
        select="count(preceding-sibling::processing-instruction())"/> 
       <xsl:variable name="numFollowing" 
        select="count(following-sibling::processing-instruction())"/> 
       <xsl:if test="$thisPosition + $numFollowing > 0"> 
        <xsl:value-of select= 
        "concat('[', $thisPosition +1, ']')"/> 
       </xsl:if> 
       </xsl:element> 
      </xsl:when> 
      <xsl:when test="self::comment()"> <!-- Comment --> 
       <xsl:element name="nodeName"> 
       <xsl:value-of select="'comment()'"/> 
       <xsl:variable name="thisPosition" 
         select="count(preceding-sibling::comment())"/> 
       <xsl:variable name="numFollowing" 
         select="count(following-sibling::comment())"/> 
       <xsl:if test="$thisPosition + $numFollowing > 0"> 
        <xsl:value-of select= 
        "concat('[', $thisPosition +1, ']')"/> 
       </xsl:if> 
       </xsl:element> 
      </xsl:when> 
      <!-- Namespace: --> 
      <xsl:when test= 
       "count(. | ../namespace::*) 
       = 
       count(../namespace::*)"> 

       <xsl:variable name="apos">'</xsl:variable> 
       <xsl:element name="nodeName"> 
       <xsl:value-of select="concat('namespace::*', 
       '[local-name() = ', $apos, local-name(), $apos, ']')"/> 

       </xsl:element> 
      </xsl:when> 
      </xsl:choose> 
     </xsl:otherwise> 
     </xsl:choose> 
    </xsl:for-each> 
    <!-- <xsl:text>&#xA;</xsl:text> --> 
    </xsl:for-each> 
</xsl:variable> 
<xsl:value-of select="$theResult"/> 
</xsl:template> 

при нанесении на поставленном XML документа:

<root name="example"> 
    <child id="1"> 
    <grandchild/> 
    </child> 
    <child id="2"/> 
    <child id="3"/> 
</root> 

продуцирует хотел, правильный результат:

/root 
/root/@name 
/root/child 
/root/child/@id 
/root/child/grandchild 
+0

Почти! Но я не хочу, чтобы в нем было нумерация [1]. Только имена узлов сами. (См. Пример.) –

+0

@ Мастерская-Алекс: Исправлено. –

+0

@Workshop: Исправлено. –

2

Я тоже решил это! Таким образом:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="fo msxsl"> 
    <xsl:output method="text" standalone="yes" encoding="UTF-8"/> 
    <xsl:param name="Detect">false</xsl:param> 
    <xsl:param name="Root"/> 
    <xsl:variable name="NewLine" select="' 
'"/> 
    <xsl:template match="/"> 
      <xsl:variable name="Nodes"> 
      <xsl:apply-templates select="/" mode="Loop"/> 
      </xsl:variable> 
      <xsl:variable name="SortedNodes"> 
      <xsl:apply-templates select="msxsl:node-set($Nodes)" mode="Nodes"> 
       <xsl:sort select="." order="ascending" case-order="lower-first" data-type="text"/> 
      </xsl:apply-templates> 
      </xsl:variable> 
      <xsl:apply-templates select="msxsl:node-set($SortedNodes)" mode="Text"/> 
    </xsl:template> 
    <!-- Elementen. --> 
    <xsl:template match="*" mode="Loop"> 
    <xsl:param name="Node"/> 
    <Node> 
     <xsl:value-of select="$Node"/>/<xsl:value-of select="name()"/> 
    </Node> 
    <xsl:apply-templates select="@*" mode="Loop"> 
     <xsl:with-param name="Node" select="concat($Node, '/', name())"/> 
    </xsl:apply-templates> 
    <xsl:apply-templates select="*" mode="Loop"> 
     <xsl:with-param name="Node" select="concat($Node, '/', name())"/> 
    </xsl:apply-templates> 
    </xsl:template> 
    <!-- Attributen. --> 
    <xsl:template match="@*" mode="Loop"> 
    <xsl:param name="Node"/> 
    <Node> 
     <xsl:value-of select="$Node"/>/@<xsl:value-of select="name()"/> 
    </Node> 
    </xsl:template> 
    <!-- Node. --> 
    <xsl:template match="Node" mode="Nodes"> 
    <xsl:if test="(1=position()) or (preceding-sibling::*[1]/. != .)"> 
     <Node> 
     <xsl:value-of select="."/> 
     </Node> 
    </xsl:if> 
    </xsl:template> 
    <xsl:template match="Node" mode="Text"><xsl:value-of select="concat(., $NewLine)"/></xsl:template> 
</xsl:stylesheet> 

Только 48 линий. :-)

+1

Мое решение было 133 строки, потому что он использует существующий код, который делает гораздо больше, чем вы хотите - мне действительно пришлось вырезать часть кода. Мое решение также может создавать пути к любому другому узлу: root ('/'), текстовые узлы, PI, комментарии и узлы пространства имен. Кроме того, мне просто нужно было 2 минуты, чтобы повторно использовать этот код, при написании с нуля даже ваше простое решение, вероятно, потребует не менее 10 минут и подвержено ошибкам, поэтому на самом деле может потребоваться гораздо больше времени. :) –