sprintf

06/07/2003 - 05:07 por Leonardo Azpurua | Informe spam
Hola.

Estoy adaptando un punto de ventas a varios idiomas. Para ello he tenido que
utilizar un archivo de recursos; hasta aquí no hay problemas.

Pero con frecuencia me encuentro en el texto original, mensajes conformados
según un patrón de texto siempre igual, por ejemplo:

ADVERTENCIA: el Codigo del Producto no puede quedar vacio
ADVERTENCIA: el precio del articulo no puede ser cero
WARNING: Item Code may not be blank
WARNING: Item Price may not be zero

es decir:

ADVERTENCIA: ??? no puede ???
WARNING: ??? may not ???

y hay al menos un patrón de este tipo para casi cada control de cada forma.
Muchos de ellos son repetitivos, pues se refieren al mismo dato en
diferentes contextos. Pero aun así, quedamos con un patrón repetitivo para
cada dato validable de cada clase.

Una solución podría ser armar cada frase utilizando encadenamiento de
cadenas, así:

sMensaje = LoadResString(STRRS_ADVERT) & _
LoadResString(STRRS_CODIGOITEM) & " " & _
LoadResString(STRRS_NOPUEDE) & " " & _
LoadResString(STRRS_SERVACIO)

pero en "C" había una función preciosa: sprintf, que simplemente rellenaba
marcadores en el primer argumento con cada uno de los argumentos siguientes
en el ordenen que se iban presentando, por ejemplo:

sprintf(sdest, LoadResString(STRRS_ADVERT),
LoadResString(STRRS_CODIGOITEM),
LoadResString(STRRS_NOPUEDE),
LoadResString(STRRS_SERVACIO));

si el recurso STRRS_ERR, estuviera definido como "ERROR: el %s %s %s", el
resultado de la instrucción anterior sería copiar en sdest, la cadena
"ERROR: el Codigo del Producto no puede quedar vacio".

Me gusta más la sintaxis de "C": es una función que asigna contenidos a
componentes dentro de una plantilla, no una secuencia de encadenamientos,
como la versión original en BASIC.

De manera que me escribí una función en VB, llamada sprintf, cuya definición
es la siguiente:

Public Function sprintf(sTemplate As String, ParamArray Args() As Variant)
As String
Dim sRetString As String
Dim nUpTo As Long, nLast As Long
Dim nIndex As Integer, sIndex As String
Dim v As Variant

nUpTo = 1: nLast = 1
Do
nUpTo = InStr(nUpTo, sTemplate, "%")
If nUpTo = 0 Then
If Len(sTemplate) >= nLast Then
sRetString = sRetString & Right(sTemplate, Len(sTemplate) - nLast +
1)
End If
Else
nUpTo = nUpTo + 1
If Mid(sTemplate, nUpTo, 1) = "%" Then
sRetString = sRetString & Mid(sTemplate, nLast, nUpTo - nLast)
nUpTo = nUpTo + 1
nLast = nUpTo
Else
' Agregar el ultimo segmento
sRetString = sRetString & Mid(sTemplate, nLast, nUpTo - nLast - 1)
nLast = nUpTo
Do While InStr(1, "0123456789", Mid(sTemplate, nUpTo, 1)) <> 0 And
nUpTo - nLast < 3
nUpTo = nUpTo + 1
Loop
If nUpTo = nLast Then
v = "¡ERR!"
Else
sIndex = Mid(sTemplate, nLast, nUpTo - nLast)
nIndex = Val(sIndex) - 1
If nIndex < LBound(Args) Or nIndex > UBound(Args) Then
v = "¡INDEX!"
Else
v = Args(nIndex)
End If
End If
sRetString = sRetString & v
nLast = nUpTo
End If
End If
Loop While nUpTo <> 0
sprintf = sRetString
End Function

y que devuelve un string, combinando los argumentos contenidos en Args con
la plantilla contenida en sTemplate.

Es diferente de sprintf en "C". En primer lugar, no escribe el resultado en
un bufer pasado por referencia, sino que lo devuelve como resultado de la
función. En segundo lugar, en "C" se especifica el tipo de dato y el formato
a aplicar a cada argumento en orden de aparición. En este sprintf se
especifica la posición del argumento a insertar en el arreglo Args, mediante
un número precedido de un signo de porcentaje.

Por ejemplo:

sprintf("Esto %1 y vuelve a %1r", "aparece"), devolverá: "Esto aparece y
vuelve a aparecer"

si se quiere insertar un signo de porcentaje, se debe escribir %%. Por
ejemplo (a = 15.5):
sprintf("Esta factura incluye un descuento del %1%%", a), devolverá: "Esta
factura incluye un descuento del 15,5%"

si el signo de porcentaje no va seguido de un número, se insertará el código
"¡ERR!" en la posición correspondiente:
sprintf("Esta factura incluye un descuento del %1%", a), devolverá: "Esta
factura incluye un descuento del 15,5¡ERR!"

por último, una referencia a un argumento inexistente (menor que uno omayor
que la cantidad de argumentos) insertrá el codigo ¡INDEX! en esa posición:
sprintf("Esta factura incluye un descuento del %10%%", a), devolverá: "Esta
factura incluye un descuento del ¡INDEX!%"

sprintf es una de esas funciones casi triviales cuya utilidad no se reconoce
hasta que no se pierde. Ahora, tres años despues, la tengo de nuevo.

Por supuesto que no la presento como una "innovación tecnológica". A los
aprendices les puede servir como ejemplo de una técnica "sucia y rápida" de
análisis sintáctico, uso de argumentos variables y proceso de cadenas en
general. Y a los más avanzados como un recurso adicional al cual echar mano
cuando se presente la necesidad.

Salud!

Leonardo
MS MVP
 

Leer las respuestas

#1 Eduardo A. Morcillo [MS MVP]
06/07/2003 - 06:54 | Informe spam
Leonardo,

Lindo ejercicio. Quizas no sabias la existencia de la API wvsprintf del
windows:

Private Declare Function wvsprintf Lib "user32" Alias "wvsprintfA" ( _
ByVal lpOutput As String, _
ByVal lpFmt As String, _
arglist As Long) As Long

Function sprintf(ByVal Fmt As String, ParamArray Params()) As String
Dim alArgList() As Long
Dim asStrs() As String
Dim lIdx As Long
Dim lStr As Long

ReDim alArgList(0 To UBound(Params()))

For lIdx = 0 To UBound(Params())

If VarType(Params(lIdx)) = vbString Then
ReDim asStrs(0 To lStr)
asStrs(lStr) = StrConv(Params(lIdx), vbFromUnicode)
alArgList(lIdx) = StrPtr(asStrs(lStr))
lStr = lStr + 1
Else
alArgList(lIdx) = Params(lIdx)
End If

Next

sprintf = Space$(1024)
sprintf = Left$(sprintf, wvsprintf(sprintf, Fmt, alArgList(0)))

End Function

MsgBox sprintf("La letra %c es %s(%d)", 65, "Chr$", 65)

Eduardo A. Morcillo [MS MVP - VB]
http://www.domaindlx.com/e_morcillo

Preguntas similares