Eu tinha duas funções quase idênticas para analisar um XML com XPath:
func xpathContent(root xml.Node, xpath string) string {
result, _ := root.Search(xpath)
if len(result) > 0 {
return strings.TrimSpace(result[0].Content())
} else {
return ""
}
}
/* and */
func xpathAttribute(root xml.Node, xpath string, attr string) string {
result, _ := root.Search(xpath)
if len(result) > 0 {
return strings.TrimSpace(result[0].Attr(attr))
} else {
return ""
}
}
Claro que há muita repetição , eles se diferenciam apenas para o primeiro ramo da if
condição. Isso significa que podemos envolver toda a lógica circundante em uma função separada e executar dinamicamente uma parte arbitrária de código (extração de conteúdo ou atributo).
Vamos reescrever com os fechamentos em mente:
type xpathFn func(node xml.Node) string
func xpathSearch(root xml.Node, xpath string, fn xpathFn) string {
result, _ := root.Search(xpath)
if len(result) > 0 {
return fn(result[0])
} else {
return ""
}
}
Em primeiro lugar, precisamos declarar um novo tipo de função, para que possamos passá-la como argumento. O segundo passo é extrair a lógica comum em uma terceira função e substituir a parte que queremos que seja dinâmica, com a invocação ( fn(result[0])
).
Agora podemos usar este novo componente para reescrever as funções anteriores:
func xpathContent(root xml.Node, xpath string) string {
return xpathSearch(root, xpath, func(node xml.Node) string {
return strings.TrimSpace(node.Content())
})
}
func xpathAttribute(root xml.Node, xpath string, attr string) string {
return xpathSearch(root, xpath, func(node xml.Node) string {
return strings.TrimSpace(node.Attr(attr))
})
}
Veja como invocamos xpathSearch
o terceiro argumento é anônimo xpathFn
e sua return
vontade se tornou o valor de retorno, apenas se a condição de pesquisa for satisfeita.
Outro aspecto que merece atenção é o escopo da função mais interna: tem acesso aos vars externos, como attr
.