package search
import (
"encoding/xml"
"fmt"
"strings"
)
// OpenSearchDescription is the root <OpenSearchDescription> element of an
// OSD 1.1 document. Browsers (Firefox, Chromium) parse this to register
// a search engine; the {searchTerms} placeholder in URL templates is
// substituted by the browser at search time.
//
// Spec: https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md
type OpenSearchDescription struct {
XMLName xml.Name `xml:"OpenSearchDescription"`
XMLNS string `xml:"xmlns,attr"`
ShortName string `xml:"ShortName"`
Description string `xml:"Description"`
InputEncoding string `xml:"InputEncoding"`
Image *Image `xml:"Image,omitempty"`
URLs []URL `xml:"Url"`
SearchForm string `xml:"SearchForm,omitempty"`
Developer string `xml:"Developer,omitempty"`
Attribution string `xml:"Attribution,omitempty"`
SyndicationRgt string `xml:"SyndicationRight,omitempty"`
AdultContent string `xml:"AdultContent,omitempty"`
}
// URL is a single <Url> element pointing at a search template.
type URL struct {
Type string `xml:"type,attr"`
Template string `xml:"template,attr"`
Method string `xml:"method,attr,omitempty"`
}
// Image is a <Image> element for the search engine icon.
type Image struct {
Height int `xml:"height,attr"`
Width int `xml:"width,attr"`
Type string `xml:"type,attr"`
Value string `xml:",chardata"`
}
const xmlNS = "http://a9.com/-/spec/opensearch/1.1/"
// DescriptionForProvider builds an OSD document that points the browser
// directly at the upstream provider — useful when the user wants to add
// e.g. urbandictionary as a standalone search engine.
func DescriptionForProvider(p Provider) OpenSearchDescription {
osd := OpenSearchDescription{
XMLNS: xmlNS,
ShortName: p.Name,
Description: p.Description,
InputEncoding: "UTF-8",
URLs: []URL{{
Type: "text/html",
// OpenSearch uses {searchTerms}; convert from our internal {q}.
Template: strings.ReplaceAll(p.SearchURL, "{q}", "{searchTerms}"),
}},
SearchForm: p.HomeURL,
}
if p.IconURL != "" {
osd.Image = &Image{Height: 16, Width: 16, Type: "image/x-icon", Value: p.IconURL}
}
return osd
}
// DescriptionForRouter builds an OSD document that points the browser at
// this service's own /search endpoint, so prefix routing ("ud foo") works
// from the browser's address bar.
//
// publicURL must be the externally-visible base URL (no trailing slash).
func DescriptionForRouter(publicURL string) OpenSearchDescription {
publicURL = strings.TrimRight(publicURL, "/")
return OpenSearchDescription{
XMLNS: xmlNS,
ShortName: "huntsman",
Description: "Multi-provider search router (ud, gh, steam)",
InputEncoding: "UTF-8",
URLs: []URL{{
Type: "text/html",
Template: fmt.Sprintf("%s/search?q={searchTerms}", publicURL),
}},
SearchForm: publicURL + "/",
}
}
// Marshal serializes an OSD document with the standard XML prolog.
func Marshal(osd OpenSearchDescription) ([]byte, error) {
body, err := xml.MarshalIndent(osd, "", " ")
if err != nil {
return nil, err
}
return append([]byte(xml.Header), body...), nil
}