Archivo de la etiqueta: c#

Ejemplos de Google Cloud Natural Language para consola y .NET MAUI

Pues eso… aquí te dejo el código de una clase (Frases) para analizar un texto usando la API de Google Cloud Natural Language y un par de proyectos para usar esa clase. Los proyectos son para una aplicación de consola y para dispositivos usando .NET MAUI. Todo el código está para C#.

No te voy a explicar mucho por aquí, salvo lo indicado en el siguiente párrafo, pero te dejo todo el código fuente (para C#) en este repositorio de GitHub.
También incluyo algunas explicaciones y problemas que he tenido para usar la clase en el proyecto para .NET MAUI (no muchos, pero…)

Algunos trucos en el código de .NET MAUI

Un par de cosas que siempre suelo poner en los proyectos para .NET MAUI, porque no tienen la misma funcionalidad que con Xamarin.Forms, son:

  1. Definir el tamaño de la ventana para Windows, ya que en .NET MAUI la ventana se muestra enorme y no recuerda el tamaño último.
    Esto lo hago en el constructor de la clase App.
  2. Hacer que el control Frame se vea al completo (no se corte por la parte inferior).
    Esto lo consigo si en el StackLayout usado después del Frame se le deja un margin mínimo de 2.

Además, he añadido el código para simular un Expander ver figura 1).
Este expander lo utilizo para mostrar u ocultar la lista de textos de prueba.

Otra cosa interesante es usar un objeto Task (usar otro proceso) cuando se pulsa en el botón de analizar, con idea que se muestre el texto mientras está analizando el texto y no se quede «congelada» la ventana.

Este es el código del evento Clicked del botón de analizar:

private async void BtnAnalizar_Clicked(object sender, EventArgs e)
{
    txtResultado.Text = "";

    string tmp = txtTexto.Text;
    if (string.IsNullOrEmpty(tmp))
    {
        MostrarAviso("Por favor indica el texto a analizar de al menos 3 caracteres", esError: true);
        txtTexto.Focus();
        return;
    }

    text = tmp;
    HabilitarBotones(false);

    await Task.Run(() =>
    {
        MostrarAviso("Analizando el texto...", esError: false);
        frase = Frases.Add(text);

        BtnMostrar2.Dispatcher.Dispatch(() =>
        {
            // Inicialmente mostrar todo sin tokens
            BtnMostrar2_Clicked(null, null);
        });
        QuitarAviso();
    });

    HabilitarBotones(true);

En los métodos llamados desde Task.Run se tienen en cuenta el Dispatcher de los controles que se modifican, con idea de que no den problemas al hacerlo entre hilos diferentes.

Este es el código de los métodos QuitarAviso y MostrarAviso que modifican una etiqueta y un StackLayout.

private void QuitarAviso()
{
    LabelAviso.Dispatcher.Dispatch(() => { LabelAviso.IsVisible = false; });
    grbAviso.Dispatcher.Dispatch(() => { grbAviso.BackgroundColor = Colors.Transparent; });
}

private void MostrarAviso(string aviso, bool esError)
{
    grbAviso.Dispatcher.Dispatch(() =>
    {
        if (esError)
        {
            grbAviso.BackgroundColor = Colors.Firebrick;
        }
        else
        {
            grbAviso.BackgroundColor = Colors.SteelBlue;
        }
    });
    LabelAviso.Dispatcher.Dispatch(() =>
    {
        LabelAviso.Text = aviso;
        LabelAviso.IsVisible = true;
    });

Algunas capturas

Aquí tienes un par de capturas de la app para .NET MAUI en funcionamiento, en la figura 1 está funcionando en Windows (usando el expander), en la figura 2 antes de poner el expander y en la figura 3 en un móvil con Android (antes de poner el expander), en iPhone no me funciona (tampoco el resto de los proyectos que tenía, así que, no he podido hacer captura).

Figura 1. En Windows con el expander

Figura 2. En Windows

Figura 3. En Android

Te recomiendo que leas el post anterior para ver cómo crear un cliente de Google Cloud Natural Language y poder usarlo en estos proyectos, en ese post indico que el código es para Visual Basic, pero los pasos a seguir son los mismos para Visual Basic que para C#.


Espero que te sea de utilidad.

Nos vemos.
Guillermo

Google Cloud Natural Language, ejemplo en Visual Basic .NET

Pues eso… aquí te dejo un ejemplo para usar las API de Google Cloud Natural Language, pero para Visual Basic .NET

Con esas API podrás analizar textos (también en español) y ver las palabras que la forman (tokens), su estructura sintáctica, etc.

Nota:
Para usar este código tendrás que crearte una cuenta en Google Cloud, generar una «key» para usarla y poco más, todos los pasos están explicados en este enlace (el código de ejemplo es para C#, pero te servirá.

Pasos para crear un proyecto usando dotnet (cli):

– Abre una ventana de consola (o terminal)
– Posicionarse en la carpeta donde crear el proyecto
– Crear el proyecto
dotnet new console -n <nombre-proyecto>
dotnet new console -lang VB -n <nombre-proyecto>
– Cambiar al directorio del proyecto
cd <nombre-proyecto>
– Añadir el paquete de Google Cloud Natural Language API
dotnet add package Google.Cloud.Language.V1
– Copiar el fichero key.json con las claves y permisos
– Ver estos pasos para crearla:
https://codelabs.developers.google.com/codelabs/cloud-natural-language-csharp#3
– En IAM, añadir la cuenta creada (incluida en el fichero key.json) en +OTORGAR ACCESO
Solo estará la principal y/o las otras añadidas
– Si se ha usado otra cuenta, estará en IAM>Cuentas de Servicio
– Modificar Program.cs (o Program.vb) para usar el código que accede a la API de Natural Language
– Ejecutar el código
dotnet run

Notas:
– Debes crear una variable de entorno en Windows, (lo puedes hacer desde la misma consola) indicando el path donde estará el fichero key.json.
– Lo que yo hago es copiar ese fichero en la carpeta del ejecutable y la variable de entorno la defino de esta forma:
set GOOGLE_APPLICATION_CREDENTIALS=key.json
– Puedes modificar el fichero del proyecto y añadir lo siguiente:

  <ItemGroup>
    <None Update="key.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

El código de ejemplo

'--------------------------------------------------------------------------------
' Ejemplo de Google Cloud Natural Language en Visual Basic .NET (31/ene/23 18.50)
'
' (c)Guillermo Som (Guille), 2023
'--------------------------------------------------------------------------------

Imports System
Imports System.Text
Imports gcl = Google.Cloud.Language.V1
Imports Google.Protobuf.Collections
Imports Google.Cloud.Language.V1.AnnotateTextRequest.Types

'Namespace NaturalLanguageApiDemo
Class Program
    Shared client As gcl.LanguageServiceClient '?

    Shared Sub Main(args As String())
        'Dim text = "El 8 de Febrero voy en bici al Camino de Santiago desde Sarria ¿crees que aguantaré?"
        Dim text = "Probando Google Cloud Natural Language con VB.NET ¿Funcionará esto?"

        Console.WriteLine("Ejemplos de Google.Cloud.Language")
        Console.WriteLine()
        Console.WriteLine("Pruebas de Google Cloud Natural Language en Visual Basic .NET")
        Console.WriteLine()
        Console.WriteLine("  Creando el cliente...")
        client = gcl.LanguageServiceClient.Create()
        Console.WriteLine()

        Dim repitiendo As Boolean = False

        Do

            If repitiendo Then
                Console.WriteLine($"Última: '{text}'")
                Console.WriteLine("Indica la frase que quieres analizar (0 salir, [la última])")
            Else
                Console.WriteLine($"Predeterminada: '{text}'")
                Console.WriteLine("Indica la frase que quieres analizar (0 salir, [predeterminada])")
            End If

            Console.Write("> ")
            Dim resText = Console.ReadLine()

            If Not String.IsNullOrEmpty(resText) Then

                If resText = "0" Then
                    Exit Do
                End If

                text = resText
            End If

            Do
                Console.WriteLine($"Analizar: '{text}'")
                Console.Write("1- Todo con tokens, 2- Todo sin tokens, 3- Solo tokens, 0- nueva frase [2] ? ")
                resText = Console.ReadLine()
                Console.WriteLine()

                If String.IsNullOrEmpty(resText) Then
                    resText = "2"
                End If

                If resText = "1" Then
                    Analizar(text, conTokens:=True)
                ElseIf resText = "2" Then
                    Analizar(text, conTokens:=False)
                ElseIf resText = "3" Then
                    AnalizarTokens(text)
                ElseIf resText = "0" Then
                    Exit Do
                End If

                Console.WriteLine()
            Loop While True

            repitiendo = True
        Loop While True
    End Sub

    Private Shared Sub Analizar(text As String, conTokens As Boolean)
        If client Is Nothing Then
            client = gcl.LanguageServiceClient.Create()
        End If

        Dim document = gcl.Document.FromPlainText(text)
        Dim response As gcl.AnnotateTextResponse

        Try
            response = client.AnnotateText(document, New Features With {
                    .ExtractSyntax = True,
                    .ExtractEntities = True,
                    .ExtractDocumentSentiment = True,
                    .ExtractEntitySentiment = True,
                    .ClassifyText = True
                })
        Catch
            response = client.AnnotateText(document, New Features With {
                    .ExtractSyntax = True,
                    .ExtractEntities = True,
                    .ExtractDocumentSentiment = True,
                    .ExtractEntitySentiment = True
                })
        End Try

        Dim sentiment = response.DocumentSentiment
        Console.WriteLine($"Detected language: {response.Language}")
        Console.WriteLine($"Sentiment Score: {sentiment.Score}, Magnitude: {sentiment.Magnitude}")
        Console.WriteLine("***Entities:")
        Dim entity1 As gcl.Entity = Nothing

        For Each entity0 In response.Entities

            If entity1 Is Nothing Then
                entity1 = entity0
            Else
                If entity0.Equals(entity1) Then Continue For
            End If

            Console.WriteLine($"Entity: '{entity0.Name}'")
            Console.WriteLine($"  Type: {entity0.Type},  Salience: {CInt((entity0.Salience * 100))}%")

            If entity0.Mentions.Count > 0 Then
                Console.WriteLine($"  Mentions: {entity0.Mentions.Count}")

                For Each mention In entity0.Mentions
                    Console.Write($"    Text: '{mention.Text.Content}' (beginOffset: {mention.Text.BeginOffset}),")
                    Console.WriteLine($" Type: {mention.Type}, Sentiment: {mention.Sentiment}")
                Next
            End If

            If entity0.Metadata.Count > 0 Then
                Console.WriteLine($"  Metadata: {entity0.Metadata}")

                If entity0.Metadata.ContainsKey("wikipedia_url") Then
                    Console.WriteLine($"    URL: {entity0.Metadata("wikipedia_url")}")
                End If
            End If
        Next

        Console.WriteLine("***Categories:")

        For Each cat In response.Categories
            Console.WriteLine($"Category: '{cat.Name}' (Confidence: {cat.Confidence})")
        Next

        Console.WriteLine("***Sentences:")

        For Each sentence In response.Sentences
            Console.WriteLine($" Sentence.Text.Content: '{sentence.Text.Content}'")
            Console.WriteLine($"   Sentence.Text.BeginOffset: {sentence.Text.BeginOffset}")
            Console.WriteLine($" Sentence.Sentiment .Magnitude: {sentence.Sentiment.Magnitude}, .Score: {sentence.Sentiment.Score}")
        Next

        If conTokens Then
            Console.WriteLine("***Tokens:")

            For i As Integer = 0 To response.Tokens.Count - 1
                MostrarToken(i, response.Tokens, conContenido:=False)
            Next
        End If
    End Sub

    Private Shared Sub AnalizarTokens(text As String)
        If client Is Nothing Then
            client = gcl.LanguageServiceClient.Create()
        End If

        Dim document = gcl.Document.FromPlainText(text)
        Dim response As gcl.AnnotateTextResponse

        Try
            response = client.AnnotateText(document, New Features With {
                    .ExtractSyntax = True,
                    .ExtractEntities = True,
                    .ExtractDocumentSentiment = True,
                    .ExtractEntitySentiment = True,
                    .ClassifyText = True
                })
        Catch
            response = client.AnnotateText(document, New Features With {
                    .ExtractSyntax = True,
                    .ExtractEntities = True,
                    .ExtractDocumentSentiment = True,
                    .ExtractEntitySentiment = True
                })
        End Try

        AnalizarSentecias(response)
    End Sub

    Private Shared Sub AnalizarSentecias(self As gcl.AnnotateTextResponse)
        Dim index As Integer = 0

        For Each sentence In self.Sentences
            Dim content = sentence.Text.Content
            Dim sentence_begin = sentence.Text.BeginOffset
            Dim sentence_end = sentence_begin + content.Length - 1

            While index < self.Tokens.Count AndAlso self.Tokens(index).Text.BeginOffset <= sentence_end
                MostrarToken(index, self.Tokens, conContenido:=True)
                index += 1
            End While
        Next
    End Sub

    Private Shared Sub MostrarToken(nToken As Integer, tokens As RepeatedField(Of gcl.Token), Optional conContenido As Boolean = True)
        Dim token As gcl.Token = tokens(nToken)
        Console.WriteLine($"{nToken}- Token: Text.Content: '{token.Text.Content}', Lemma: '{token.Lemma}'")

        If token.DependencyEdge.Label = gcl.DependencyEdge.Types.Label.Root Then
            Console.Write($"  **DependencyEdge Label: {token.DependencyEdge.Label}")

            If token.DependencyEdge.HeadTokenIndex <> nToken Then
                Console.Write($", HeadTokenIndex: {token.DependencyEdge.HeadTokenIndex}")
            End If

            Console.WriteLine("**")
        Else
            Console.Write($"  DependencyEdge Label: {token.DependencyEdge.Label}, HeadTokenIndex: {token.DependencyEdge.HeadTokenIndex}")
            Dim tokenDependency = tokens(token.DependencyEdge.HeadTokenIndex)
            Console.WriteLine($" ('{tokenDependency.Text.Content}')")
        End If

        If conContenido Then
            Console.WriteLine($"  PartOfSpeech:")
            Console.Write($"    Tag: {token.PartOfSpeech.Tag},")
            Dim sb = New StringBuilder()

            If token.PartOfSpeech.Aspect <> gcl.PartOfSpeech.Types.Aspect.Unknown Then
                sb.Append($" (Aspect: {token.PartOfSpeech.Aspect},")

                If token.PartOfSpeech.[Case] <> gcl.PartOfSpeech.Types.[Case].Unknown Then
                    sb.Append($" Case: {token.PartOfSpeech.[Case]},")
                End If

                If token.PartOfSpeech.Form <> gcl.PartOfSpeech.Types.Form.Unknown Then
                    sb.Append($" Form: {token.PartOfSpeech.Form},")
                End If

                If sb.ToString().EndsWith(","c) Then
                    sb.Length -= 1
                End If

                sb.Append("),")
            End If

            If token.PartOfSpeech.Gender <> gcl.PartOfSpeech.Types.Gender.Unknown Then
                sb.Append($" (Gender: {token.PartOfSpeech.Gender},")

                If token.PartOfSpeech.Mood <> gcl.PartOfSpeech.Types.Mood.Unknown Then
                    sb.Append($" Mood: {token.PartOfSpeech.Mood},")
                End If

                If token.PartOfSpeech.Number <> gcl.PartOfSpeech.Types.Number.Unknown Then
                    sb.Append($" Number: {token.PartOfSpeech.Number},")
                End If

                If sb.ToString().EndsWith(","c) Then
                    sb.Length -= 1
                End If

                sb.Append("),")
            End If

            If token.PartOfSpeech.Proper <> gcl.PartOfSpeech.Types.Proper.Unknown Then
                sb.Append($" Proper: {token.PartOfSpeech.Proper}")
            End If

            If sb.ToString().Trim().Length > 0 Then
                Console.WriteLine(sb.ToString().TrimEnd(","c))
            End If

            sb.Clear()
            sb.Append("   ")

            If token.PartOfSpeech.Person <> gcl.PartOfSpeech.Types.Person.Unknown Then
                sb.Append($" Person: {token.PartOfSpeech.Person},")
            End If

            If token.PartOfSpeech.Reciprocity <> gcl.PartOfSpeech.Types.Reciprocity.Unknown Then
                sb.Append($" Reciprocity: {token.PartOfSpeech.Reciprocity},")
            End If

            If token.PartOfSpeech.Tense <> gcl.PartOfSpeech.Types.Tense.Unknown Then
                sb.Append($" Tense: {token.PartOfSpeech.Tense},")
            End If

            If token.PartOfSpeech.Voice <> gcl.PartOfSpeech.Types.Voice.Unknown Then
                sb.Append($" Voice: {token.PartOfSpeech.Voice}")
            End If

            If sb.ToString().Trim().Length > 0 Then
                Console.WriteLine(sb.ToString())
            End If
        Else
            Console.WriteLine($"  PartOfSpeech Aspect: {token.PartOfSpeech.Aspect}, Case: {token.PartOfSpeech.[Case]}, Form: {token.PartOfSpeech.Form}")
            Console.WriteLine($"  PartOfSpeech Gender: {token.PartOfSpeech.Gender}, Mood: {token.PartOfSpeech.Mood}, Number: {token.PartOfSpeech.Number}")
            Console.WriteLine($"  PartOfSpeech Person: {token.PartOfSpeech.Person}, Proper: {token.PartOfSpeech.Proper}")
            Console.WriteLine($"  PartOfSpeech Reciprocity: {token.PartOfSpeech.Reciprocity}, Tag: {token.PartOfSpeech.Tag}")
            Console.WriteLine($"  PartOfSpeech Tense:: {token.PartOfSpeech.Tense}, Voice: {token.PartOfSpeech.Voice}")
        End If
    End Sub
End Class
'End Namespace

Una captura

La aplicación en funcionamiento

Código fuente

El código fuente del ejemplo para Visual Basic .NET, así como el de C#, los puedes ver/descargar desde este repositorio en GitHub.

Y esto ha sido todo amigos… 😉

Nos vemos.
Guillermo

Evaluar expresiones (código en Java y C#)

Pues eso… Sí, has leído bien el título: en Java. Y no en JavaScript (aparte de en C#).

Y es que hace unos días, empecé a estudiar esto de Java a raíz que mi hijo David está haciendo un curso de Java (back-end) y era por si le echaba una mano en algunos conceptos, ya que lo que él estudio en su día fue COBOL, algo de Pascal y algo de Visual Basic 6, y claro… los conceptos de POO y esas cosas, como que no las tiene claras.

Y para poder ayudarle (si es que así me lo hace saber), me puse a «trastear» en Java, y de camino he aprendido entre otras cosas, que hay diferencias, algunos grandes, en comparación con C# que es lo más parecido, (con Visual Basic ni te cuento).

Algunas de esas diferencias son a favor de Java, y la mayoría, a favor de C# o si lo prefieres a favor de .NET, seguramente porque lo conozco más.

Pero sea como sea, y peleándome un poco con el código y el IDE, que por cierto estoy usando uno que me parece muy bueno y es gratis: IntelliJ IDEA (Community Edition) de JetBrains, he conseguido hacer algo más o menos operativo y que me está enseñando algunas de esas cosas que ni siquiera sabía que C# las tenía y otras que al parecer C# tiene porque Java las tenía.

Lo que más he echado en falta son las «tuplas», aunque en este código de evaluar expresiones aritméticas he solventado con records, más que nada porque en algunas funciones necesitaba devolver dos valores (seguramente en Java habrá otra forma de hacerlo, pero no la he encontrado). Otra cosa que he echado en falta en Java es las estructuras (tipos de datos definibles, pero por valor).

 

No te voy a explicar «paso a paso» el código ni nada de eso (al menos por ahora), simplemente te voy a decir lo que este evaluador sabe hacer (creo que lo hace bien) y el código lo podrás ver en el repositorio de GitHub.

Quiero comentarte que primero hice una versión usando records para hacer las operaciones de suma, restas, multiplicación y división (basándome en un ejemplo de la documentación de Java), y el último es usando tipos double que además de esas cuatro operaciones hace un par de ellas más: módulo y factorial.

El enlace de GitHub o, mejor dicho, los enlaces son para el código basado en el tipo double, en esos repositorios tienes los enlaces al código fuente que usa los records, tanto para Java como para C#.

 

Lo que hace el evaluador de expresiones

  • Evalúa expresiones entre paréntesis (con varios niveles de anidación).
  • Evalúa primero los operadores multiplicativos (* y x para multiplicar, / y : para dividir y % para el módulo) y después los operadores aditivos (+ para sumar, – para restar).
  • La expresión puede tener espacios, pero al evaluarla se quitan, por tanto: «1 5 * 2» se convierte en «15*2».
  • Si hay un paréntesis de apertura precedido por un dígito o de un paréntesis de cierre, se considera una multiplicación y se pone el signo *.
  • Si hay un paréntesis de cierre seguido de un dígito o de un paréntesis de apertura, se considera una multiplicación y se pone el signo *.
  • Evalúa factoriales (usando el carácter «!»), tanto de números enteros positivos (naturales) como números negativos con parte decimal.
  • El valor de la factorial para números no naturales se calcula usando la función gamma basada en un código adaptado de un ejemplo de StackOverflow.

 

El código de Java y C# en GitHub

Estos son los enlaces del código fuente que tengo publicado en GitHub:

EvaluarExpresiones-java y EvaluarExpresiones-csharp.

Espero que te sea de utilidad.

Nos vemos.
Guillermo