Función robusta para eliminar etiquetas HTML

Un usuario Pregunto ✅

unicornio

Hola, tengo una columna de datos HTML que necesito mostrar como texto. Necesito deshacerme de todas las etiquetas HTML y sustituir los caracteres HTML reservados. Estaba tratando de eliminar recursivamente todas las etiquetas HTML primero. Luego planeo crear una tabla de los caracteres reservados más comunes, los valores para reemplazarlos y generar una lista para llamar a reemplazar para cada fila en esa tabla.

Todavía soy nuevo en M, pero encontré esto para reemplazar las etiquetas HTML. Excepto que solo elimina una sola etiqueta, lo que no es útil.

https://social.technet.microsoft.com/Forums/en-US/7ec64d6d-c3fc-4110-94c7-2e0087171475/how-to-remove…)

Pero cuando trato de expandir eso a una función recursiva, obtengo un error de desbordamiento de pila. Planeo comenzar de nuevo para tratar de aprender M, pero espero que alguien pueda ayudarme mientras tanto con una solución rápida para descubrir qué hice mal. A continuación se muestra mi función. Gracias.

dejar
removeOne = (entrada) =>
dejar
texto = Texto.De(entrada),
longitud = Texto.Longitud(texto),
posición = Texto.PosiciónDe(texto, «<"),
positionEnd = Texto.PosiciónDe(texto, «>»),
rango = posición Posición-final+1,
resultado = si la posición >= 0 entonces Text.ReplaceRange(text, position, range, «») otra entrada
en
resultado,

removeAll = (entrada) =>
dejar
rmvUno = eliminarUno(entrada),
rmvAll = si Text.PositionOf(rmvOne, «l») >= 0 entonces @removeAll(rmvOne) else rmvOne
en
rmv todo,

// Fuente = obtener de la base de datos,
Fuente = «

hola, soy un mensaje de texto para reemplazar, lol

«,

// retorno = eliminarTodo([columnToPassToFunction])
return = removeAll(Fuente)
en
regreso

Marcel Beug

Solo cambia el «|» en «<"

rmvAll = si Texto.PosiciónDe(rmvUno, «<«) >= 0 entonces @removeAll(rmvOne) else rmvOne

De lo contrario, puede obtener resultados inesperados si la cadena contiene < o > que no forman parte de una etiqueta HTML.

Si está interesado, puedo compartir mi código que solo elimina pares de etiquetas, es decir cadenas precedidas por el mismo sin /: <...>

Stejin

Aquí hay un enfoque diferente que usa la función Web.Page incorporada.

Debajo de la función personalizada, reemplace los caracteres especiales como o $amp; y, además, analiza y combina texto en etiquetas HTML.

(Text as any) => let
        Source = Text,
        Html = Web.Page(Source),
        resolve = (t as table) =>
            let
                Result = List.Accumulate(Table.ToRecords
vsslasd1

He leído esto y todavía estoy un poco confundido.

Solo quiero agregar una nueva columna de texto basada en una columna basada en HTML y eliminar las etiquetas HTML como se describe. No estoy seguro de cómo agregar una función personalizada.

¿Se puede hacer todo esto en un solo paso, dentro de la creación de la nueva columna?

Mi nuevo nombre de columna es: NoteText, que se basa en la columna NoteHTML

Gracias

omtinol
En respuesta a vsslasd1

Agregue una función como lo haría con una consulta estándar.

1. Cree una nueva consulta en blanco. Nómbrelo como "Limpiador de HTML".

2. En el Editor avanzado, pegue lo siguiente:

(HTML como texto) =>
dejar
Fuente = Texto.De(HTML),
SplitAny = Texto.SplitAny(Fuente,"<>"),
ListaAlternativa = Lista.Alternativa(DividirCualquiera,1,1,1),
ListSelect = List.Select(ListAlternate, each _<>" "),
TextCombine = Text.Combine(ListSelect, " ")
en
Combinación de texto

3. Cierre el Editor.

4. En su consulta principal, abra el Editor avanzado.

5. Agregue el siguiente paso:

#"Limpieza de HTML" = Table.AddColumn(#"Paso anterior", "NotaTexto", cada uno
Si [NoteHTML] = nulo
entonces nulo
más #"Limpiador de HTML"([NoteHTML])),

Buena suerte.

vsslasd1
En respuesta a omtinol

Gracias, pero desafortunadamente, no hubo suerte. Recibo más de 4000 errores.

Esta es la sintaxis que tengo:

dejar
Fuente = OData.Feed("https://xyzasdf1111.api.crm.dynamics.com/api/data/v9.1", nulo, [Implementation="2.0"]),
new_arcollectioncalls_table = Fuente{[Name="new_arcollectioncalls",Signature="table"]}[Data],
#"Nuevo_propietario_actual ampliado" = Table.ExpandRecordColumn(nueva_tabla_de_llamadas_arcolección, "nuevo_propietario_actual", {"nombre completo"}, {"nuevo_propietario_actual.nombre_completo"}),
#"New_BillingMgr ampliado" = Table.ExpandRecordColumn(#"Nuevo_propietario actual ampliado", "new_BillingMgr", {"fullname"}, {"new_BillingMgr.fullname"}),
#"Nuevas_anotaciones_de_llamada_de_arcolección ampliadas" = Table.ExpandTableColumn(#"Nuevas_anotaciones_de_llamada_de_colección_ampliadas", "nuevas_anotaciones_de_llamada_de_colección", {"creado en", "_valor_id_propietario", "texto de nota"}, {"anotaciones_nuevas_llamada_arcolección.creado en", "nuevas_anotaciones_llamada_arcolección._valor_id_propietario", "nuevas_anotaciones_llamada_arcolección. nota de texto"}),
#"Columnas eliminadas" = Table.RemoveColumns(#"Expanded new_arcollectioncall_Annotations",{"new_arcollectioncall_Annotations._ownerid_value"}),
#"Dividir columna por delimitador" = Table.SplitColumn(Table.TransformColumnTypes(#"Columnas eliminadas", {{"new_arcollectioncall_Annotations.createdon", type text}}, "en-US"), "new_arcollectioncall_Annotations.createdon", Splitter. SplitTextByEachDelimiter({" "}, QuoteStyle.Csv, false), {"new_arcollectioncall_Annotations.createdon.1", "new_arcollectioncall_Annotations.createdon.2"}),
#"Tipo cambiado" = Table.TransformColumnTypes(#"Dividir columna por delimitador",{{"new_arcollectioncall_Annotations.createdon.1", escriba fecha}, {"new_arcollectioncall_Annotations.createdon.2", escriba hora}}),
#"Columnas eliminadas1" = Table.RemoveColumns(#"Tipo cambiado",{"new_arcollectioncall_Annotations.createdon.2"}),
#"Tipo modificado1" = Table.TransformColumnTypes(#"Columnas eliminadas1",{{"new_arcollectioncall_Annotations.createdon.1", type date}}),
#"Filas filtradas" = Table.SelectRows(#"Tipo modificado1", cada uno ([new_status] = verdadero)),
#"Otras columnas eliminadas" = Table.SelectColumns(#"Filas filtradas",{"nuevo_legal", "_nuevo_valor_de_propietario actual", "_valor_de_identificación_de_propietario", "nuevas_notas_de_colección", "nuevas_colecciones", "_creado en nombre de_valor", "nuevo_número_de_cliente", "creado en", "usuariopropietario", "equipopropietario", "id_propietario", "nuevas_anotaciones_llamada_arcolección.creado en.1", "nuevas_anotaciones_llamada_arcolección.notatexto", "nuevo_BillingMgr.nombre completo", "nuevo_propietario_actual.nombre completo"}),
#"Columnas renombradas" = Table.RenameColumns(#"Otras columnas eliminadas",{{"new_arcollectioncall_Annotations.notetext", "NoteHTML"}}),

#"Limpieza de HTML" = Table.AddColumn(#"Columnas renombradas", "Texto de nota simple", cada una
Si [NoteHTML] = nulo
entonces nulo
más #"Limpiador de HTML"([NoteHTML]))
en
#"Limpieza de HTML"

Y la definición de "Consulta en blanco":

"(HTML como texto) =>#(cr)#(lf)let#(cr)#(lf)Source = Text.From(HTML),#(cr)#(lf)SplitAny = Text.SplitAny(Source, ""<>""),#(cr)#(lf)ListAlternate = List.Alternate(SplitAny,1,1,1),#(cr)#(lf)ListSelect = List.Select(ListAlternate, each _< >"" ""),#(cr)#(lf)TextCombine = Text.Combine(ListSelect, "" "")#(cr)#(lf)in#(cr)#(lf)TextCombine"

pbi.png

omtinol
En respuesta a vsslasd1

Desafortunadamente, sin ver los errores y los datos que los generan, no podré ayudar mucho más. Quizás podría crear una tabla 'ficticia' en la consulta, para incluir filas de datos que generan errores y filas que no. Usa el siguiente código:

dejar

Tabla = #tabla( tipo tabla
[
#"NoteHTML" = text
],
{
{"Datos que arrojan un error"},
{"Datos que arrojan un error"},
{nulo},
{"Datos que no"}
}
),

#"Limpieza de HTML" = Table.AddColumn(Table, "PlainNoteText", each
Si [NoteHTML] = nulo
entonces nulo
más #"Limpiador de HTML"([NoteHTML]), teclee el texto)

en
#"Limpieza de HTML"

excelente poeta

Hola,

Tengo el mismo problema que tú, pero no estoy seguro de dónde debo copiar y pegar esa función. Tengo una tabla con varias columnas y solo necesito limpiar los datos de las ETIQUETAS HTML en una de ellas. ¿Podría explicar con más detalles cómo lo hizo? eso

Atentamente,

Nicolás

Marcel Beug

Solo cambia el "|" en "<"

rmvAll = si Texto.PosiciónDe(rmvUno, "<") >= 0 entonces @removeAll(rmvOne) else rmvOne

De lo contrario, puede obtener resultados inesperados si la cadena contiene < o > que no forman parte de una etiqueta HTML.

Si está interesado, puedo compartir mi código que solo elimina pares de etiquetas, es decir cadenas precedidas por el mismo sin /: <...>

unicornio
En respuesta a Marcel Beug

Dispara, lo siento gracias. Estaba haciendo muchas pruebas y comencé con una función recursiva para eliminar 'L'. Le daré una oportunidad al código completo ahora. Además, un buen punto sobre los pares de etiquetas. Si no le importa, me encantaría revisar su código para mejorar esto.

Marcel Beug
En respuesta a unicornio

Aquí está mi código. Debe llamarse con StartPosition 0. En cada iteración, el código examina la cadena desde esa posición de inicio en busca de pares de etiquetas de código, los elimina si los encuentra y llama a la próxima iteración con una nueva StartPosition, por lo que el código también puede manejar la situación que la cadena incluye una etiqueta final sin la correspondiente etiqueta inicial, o .

Tenga en cuenta que también incluí un mecanismo para detenerse después de un número máximo de iteraciones, lo que es especialmente útil durante el desarrollo, para evitar iteraciones interminables (hasta el desbordamiento de la pila).

        fnRHTMLT = (String as text, StartPosition as number, optional Iteration as number, optional MaxIterations as number) as text =>
        let
            StringFromStartposition = Text.RemoveRange(String, 0, StartPosition),
            StartPositionEndTag = Text.PositionOf(StringFromStartposition, "</"),
            PositionsEndTag = if StartPositionEndTag = -1 
                                then -1 
                                else Text.PositionOf(Text.RemoveRange(StringFromStartposition, 0, StartPositionEndTag),">"),
            StartTag = if PositionsEndTag = -1 
                       then null 
                       else "<" & Text.Range(StringFromStartposition, StartPositionEndTag + 2, PositionsEndTag - 1),
            StartPositionStartTag = if PositionsEndTag = -1 
                                    then -1 
                                    else Text.PositionOf(Text.Start(String,StartPosition + StartPositionEndTag),StartTag,Occurrence.Last),
            NewString = if StartPositionStartTag = -1 
                        then String 
                        else Text.RemoveRange(Text.RemoveRange(String,StartPosition + StartPositionEndTag, PositionsEndTag + 1),StartPositionStartTag, PositionsEndTag),
            NextStartPosition = if PositionsEndTag = -1
                                then -1
                                else if StartPositionStartTag = -1
                                     then StartPosition + StartPositionEndTag + 1
                                     else StartPosition + StartPositionEndTag - PositionsEndTag,
            Result = if NextStartPosition = -1
                     then NewString
                     else if Iteration = null 
                          then @fnRHTMLT(NewString, NextStartPosition)
                          else if Iteration = MaxIterations
                               then NewString
                               else @fnRHTMLT(NewString, NextStartPosition, Iteration + 1, MaxIterations)

        in
            Result

mmayer

En respuesta a Marcel Beug

Intenté incorporar su código para limpiar el HTML en solo una de mis columnas. Pero me sale el siguiente error: "Expresión.Error: 1 argumentos fueron pasados ​​a la función que espera entre 2 y 4.

Detalles:

patrón =

Argumentos=Lista"

Este es el HTML que estoy tratando de eliminar del [Notes] columna:

Antes:

La solicitud incluye: indicador de estado de los costos del proyecto y el progreso del trabajo, notificación por correo electrónico a los supervisores de elementos pendientes/incompletos.
Se ha enviado TSR. Esperando en TEC.
Limpieza de SWS y actualización de datos en curso en preparación de modificaciones del sistema.
Sistema limpio y listo para trabajar con TEC en renovación.

Después:

La solicitud incluye: indicador de estado para los costos del proyecto y el progreso del trabajo, notificación por correo electrónico a los supervisores de elementos pendientes/incompletos. Se ha enviado TSR. Esperando en TEC. Limpieza de SWS y actualización de datos en curso en preparación de modificaciones del sistema. Sistema limpio y listo para trabajar con TEC en renovación.

Aquí está el código:

let
  Source = SharePoint.Tables("https://orgname.sharepoint.com/sites/department/", [ApiVersion = 15]),
    #"2be78719-1e12-4827-8f88-d9edd1a7781f" = Source{[Id="2be78719-1e12-4827-8f88-d9edd1a7781f"]}[Items],
    #"Renamed Columns" = Table.RenameColumns(#"2be78719-1e12-4827-8f88-d9edd1a7781f",{{"ID", "ID.1"}}),
    #"Changed Type" = Table.TransformColumnTypes(#"Renamed Columns",{{"End Date", type date}, {"Start Date", type date}, {"Today", type date}, {"End Date (Actu", type date}, {"Created", type date}, {"Modified", type date}, {"EndDateT", type text}, {"Completion", Int64.Type}, {"Id", Int64.Type}, {"ID.1", Int64.Type}, {"EditorId", Int64.Type}}),
      fnRHTMLT = (String as text, StartPosition as number, optional Iteration as number, optional MaxIterations as number) as text =>
        let
            StringFromStartposition = Text.RemoveRange(String, 0, StartPosition),
            StartPositionEndTag = Text.PositionOf(StringFromStartposition, "</"),
            PositionsEndTag = if StartPositionEndTag = -1 
                                then -1 
                                else Text.PositionOf(Text.RemoveRange(StringFromStartposition, 0, StartPositionEndTag),">"),
            StartTag = if PositionsEndTag = -1 
                       then null 
                       else "<" & Text.Range(StringFromStartposition, StartPositionEndTag + 2, PositionsEndTag - 1),
            StartPositionStartTag = if PositionsEndTag = -1 
                                    then -1 
                                    else Text.PositionOf(Text.Start(String,StartPosition + StartPositionEndTag),StartTag,Occurrence.Last),
            NewString = if StartPositionStartTag = -1 
                        then String 
                        else Text.RemoveRange(Text.RemoveRange(String,StartPosition + StartPositionEndTag, PositionsEndTag + 1),StartPositionStartTag, PositionsEndTag),
            NextStartPosition = if PositionsEndTag = -1
                                then -1
                                else if StartPositionStartTag = -1
                                     then StartPosition + StartPositionEndTag + 1
                                     else StartPosition + StartPositionEndTag - PositionsEndTag,
            Result = if NextStartPosition = -1
                     then NewString
                     else if Iteration = null 
                          then @fnRHTMLT(NewString, NextStartPosition)
                          else if Iteration = MaxIterations
                               then NewString
                               else @fnRHTMLT(NewString, NextStartPosition, Iteration + 1, MaxIterations)

        in
            Result,      
    #"RemoveHTML" = Table.TransformColumns(#"Changed Type",{{"Notes", fnRHTMLT, type text}}),

in
    RemoveHTML

Grumelo

En respuesta a Marcel Beug

Esta solución es capaz de eliminar etiquetas como

<p> </p>

pero no algo como

<div class="..."> </div>

Un ejemplo de llamada de función sería bienvenido. No entiendo cuál es el significado de "Iteración".

¿Es la cantidad de etiquetas que desea eliminar?

Grumelo

En respuesta a Grumelo

Mi solución para limpiar etiquetas HTML

let
   TextFromHtml = (HTML as any) =>
let
    Source = if HTML = null then
        ""
    else
        Text.From(HTML),
    SplitAny = Text.SplitAny(Source,"<>"),
    ListAlternate = List.Alternate(SplitAny,1,1,1),
    ListSelect = List.Select(ListAlternate, each _<>""),
    TextCombine = Text.Combine(ListSelect, "")
in
    TextCombine
in
    TextFromHtml

Para principiantes: cree una consulta en blanco y copie y pegue el código anterior; cambie el nombre de la consulta como "TextFromHtml"

Ejemplo de llamar a esta función en una columna llamada Comentario:
= Table.TransformColumns(#"Paso anterior",{{"Comentario", TextFromHtml, escriba texto}})

Referencia: https://social.technet.microsoft.com/Forums/office/en-US/b080d122-14f0-43bc-8a86-f50cdf1c32d8/removi...

omtinol

En respuesta a Marcel Beug

Acabo de probar tu código, pero recibí el error "No se reconoció el nombre 'Iteración'. Asegúrate de que esté escrito correctamente". ¿Alguna sugerencia?

Marcel Beug

En respuesta a omtinol

Probablemente no copiaste mi código correctamente.

De lo contrario: el código debe estar precedido por un "let" adicional y debe agregarse "in fnRHTMLT" al final:

let
    

    <code block: see above>


in
    fnRHTMLT

omtinol

En respuesta a Marcel Beug

OK, gracias, eso funcionó. Pero no elimina todo el HTML. Se limpió , ,

    ,

, y algunas otras cadenas de uno y dos caracteres, pero pasó por alto
, , y largo , y instrumentos de cuerda.

No veo nada en el código que filtre algunas cadenas de uno o dos caracteres, pero no otras. ¿Cómo puedo adaptar el código para encontrar cadenas de cualquier longitud?

Marcel Beug

En respuesta a omtinol

El código no mira las longitudes, en otras palabras, las longitudes no importan.

El código busca pares de etiquetas como

y

En general: una etiqueta de inicio sin barra oblicua y una etiqueta final con barra oblicua /.

Las etiquetas que no vienen en esos pares no se eliminan.

Puede considerar una solución en la que se elimine cualquier cosa entre "<" y ">" (incluidos estos delimitadores), pero el riesgo es que se eliminen los caracteres que no forman parte de una etiqueta HTML.

Puede encontrar un ejemplo en Technet.

Max01

En respuesta a Marcel Beug

Hola,

Soy muy nuevo en Power BI y trato de usar la solución publicada anteriormente, pero no estoy seguro de qué bits reemplazar y exactamente con qué reemplazarlos. Pegué la consulta en el editor avanzado e intenté reemplazar 'cadena como texto' con el nombre de mi columna, pero no funciona.

¿Puedes ayudarme explicando?

Gracias,

máx.

omtinol

En respuesta a Marcel Beug

Acabo de probar tu código, pero recibí el error "No se reconoció el nombre 'Iteración'. Asegúrate de que esté escrito correctamente". ¿Alguna sugerencia?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *