¿Cómo leer el código HTML como XML y obtener la salida como la muestra en sql?

11

Tengo un código HTML almacenado en la base de datos y quiero leerlo como XML.

Mis códigos:

http://rextester.com/RMEHO89992

Este es un ejemplo del código HTML que tengo:

<div>
  <section>
       <h4>
         <span> A </span>
        </h4>
        <ul>
           <li>
              <span> Ab</span>
                     AD
              <span> AC </span>
           </li>
           <li>
              <span> Ag</span>
              <span> AL </span>
           </li>
        </ul>
        <h4>
              <span> B </span>
       </h4>
       <ul>
           <li>
              <span> Bb</span>
                     BD
              <span> BC </span>
           </li>
           <li>
              <span> Bg</span>
              <span> BL </span>
           </li>
        </ul>
   </section>
</div>

y este es un ejemplo de la salida que necesito:

Category         Selection        Value                    
---------        ---------        ------------             
A                Ab               AD                  
A                Ag               AL                       
B                Bb               BD                   
B                Bg               BL                       

Necesito obtener el valor dentro de la <h4>etiqueta como a Category, la primera <span>etiqueta como Selección y el resto de los valores como una cadena concatenada.

He intentado la siguiente consulta:

SELECT 
    (  isnull(t.v.value('(h4/span/span[1]/text())[1]','nvarchar(max)'),'') 
     + isnull(t.v.value('(h4/span/text())[1]','nvarchar(max)'),'')
     + isnull(t.v.value('(h4/span/span[2]/text())[2]','nvarchar(max)'),'')
    ) AS [Category],
    (  isnull(c.g.value('(span[1]/text())[1]','nvarchar(max)'),'')
     + isnull(c.g.value('(span[1]/span/text())[1]','nvarchar(max)'),'')
     + isnull(c.g.value('(span[1]/text())[2]','nvarchar(max)'),'')
    ) AS [Selection],
    (  isnull(c.g.value('(span[2]/text())[1]','nvarchar(max)'),'')
     + isnull(c.g.value('(span[2]/span/text())[1]','nvarchar(max)'),'')
     + isnull(c.g.value('(span[2]/text())[2]','nvarchar(max)'),'')
    ) AS [Value]
FROM @htmlXML.nodes('div/section') as t(v)
CROSS APPLY t.v.nodes('./ul/li') AS c(g) 

y :

SELECT 
       t.v.value('.','nvarchar(max)')
       ,
     --( isnull(t.v.value('(h4/span/span[1]/text())[1]','nvarchar(max)'),'')+isnull(t.v.value('(h4/span/text())[1]','nvarchar(max)'),'')+isnull(t.v.value('(h4/span/span[2]/text())[2]','nvarchar(max)'),''))AS [Category],

          ( isnull(c.g.value('(span[1]/text())[1]','nvarchar(max)'),'')+isnull(c.g.value('(span[1]/span/text())[1]','nvarchar(max)'),'')+isnull(c.g.value('(span[1]/text())[2]','nvarchar(max)'),''))AS [Selection]

           ,
         ( isnull(c.g.value('(span[2]/text())[1]','nvarchar(max)'),'')+isnull(c.g.value('(span[2]/span/text())[1]','nvarchar(max)'),'')+isnull(c.g.value('(span[2]/text())[2]','nvarchar(max)'),''))AS [Value]
    FROM    @htmlXML.nodes('div/section/h4/span') as t(v)
    CROSS APPLY @htmlXML.nodes('div/section/ul/li') AS c(g)

Pero solo obtiene la primera categoría y no obtiene todos los valores juntos.

Category         Selection        Value
---------        ---------        ------------
A                Ab               AC 
B                Ab               AC 
A                Ag               AL
B                Ag               AL 
A                Bb               BC
B                Bb               BC 
A                Bg               BL 
B                Bg               BL 

Puede haber N categorías, y los valores pueden o no estar dentro de las <span>etiquetas. ¿Cómo puedo obtener todas las categorías con su valor correspondiente? o obtener:

category              h4 number
--------            -----------
 A                     1
 B                     2
  • 1, media = h4 primero, 2, media = h4 segundo
 ul number         Selection        Value                    
    ---------        ---------        ------------             
    1                Ab               AD                  
    1                Ag               AL                       
    2                Bb               BD                   
    2                Bg               BL       

relación entre el número ul de la columna y el número h4. No puedo.

Ejército Rojo
fuente
1
¿Estás seguro de que el resultado esperado es correcto? ¿No debería ser AD ACpara la primera fila de la tercera columna?
Mikael Eriksson
Estoy tratando de establecer comunicación entre los nodos `h4` y` ul`.
RedArmy

Respuestas:

7

Esto no es exactamente elegante, pero parece hacer el trabajo.

DECLARE @X XML = REPLACE(REPLACE(@S, '<h4>', '<foo><h4>'), '</ul>', '</ul></foo>')

SELECT Category = x.value('../../h4[1]/span[1]', 'varchar(10)'),
       Selection = x.value('descendant-or-self::text()[1]', 'varchar(10)'),
       Value = REPLACE(
                REPLACE(
                 REPLACE(
                  LTRIM(
                   RTRIM(
                    REPLACE(
                     REPLACE(
                      CAST(x.x.query('fn:data(descendant-or-self::text()[fn:position() > 1])') AS VARCHAR(MAX))
                     , char(10), '')
                    , char(13), '')
                   )
                  )
                 , '  ', ' |')
                , '| ', '')
               , '|', '')
FROM   @X.nodes('div/section/foo/ul/li') x(x)
ORDER  BY Category,
          Selection

Que vuelve

+----------+-----------+-------+
| Category | Selection | Value |
+----------+-----------+-------+
|  A       |  Ab       | AD AC |
|  A       |  Ag       | AL    |
|  B       |  Bb       | BD BC |
|  B       |  Bg       | BL    |
+----------+-----------+-------+

Supongo que esto es lo que desea, ya que la tabla de resultados deseada en la pregunta no devuelve el "resto de los valores como una cadena concatenada"

Martin Smith
fuente
14

Estoy tratando de establecer comunicación entre nodos h4y ul.

Puede usar el operador <<y >>para verificar si un nodo está antes o después de otro nodo en el orden de los documentos. Combine eso con un predicado en posición, [1]para obtener la primera aparición también en orden de documento.

select H4.X.value('(span/text())[1]', 'varchar(10)') as Section,
       UL.X.query('.') as UL
from @X.nodes('/div/section/h4') as H4(X)
  cross apply H4.X.nodes('(let $h4 := . (: Save current h4 node :)
                           return /div/section/ul[$h4 << .])[1]') as UL(X);

rextester:

<<y >>se llaman Operadores de comparación de orden de nodo

Si tiene un fragmento XML como este:

<N1>1</N1>
<N2>2</N2>
<N3>3</N3>
<N4>4</N4>
<N5>5</N5>

Puede obtener todos los nodos antes de la primera aparición de N3con esta consulta:

select @X.query('/*[. << /N3[1]]');

Resultado:

<N1>1</N1>
<N2>2</N2>

/*le dará todos los nodos raíz. Lo que está encerrado []es un predicado. .es el nodo actual y /N3[1]es el primer nodo N3 en el orden de los documentos en el nivel raíz. Entonces, de cada nodo raíz obtienes los nodos que preceden N3.

Aquí está casi la misma consulta, solo usted obtiene los nodos que siguen al primer N3nodo:

select @X.query('/*[. >> /N3[1]]');
<N4>4</N4>
<N5>5</N5>

Para obtener solo el primer nodo después del primer N3nodo, agregue el predicado [1]:

select @X.query('/*[. >> /N3[1]][1]');
<N4>4</N4>
Mikael Eriksson
fuente