srcset.go 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. // Copyright 2022 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package sanitizer
  5. import (
  6. "fmt"
  7. "strconv"
  8. "strings"
  9. )
  10. type ImageCandidate struct {
  11. ImageURL string
  12. Descriptor string
  13. }
  14. type ImageCandidates []*ImageCandidate
  15. func (c ImageCandidates) String() string {
  16. var htmlCandidates []string
  17. for _, imageCandidate := range c {
  18. var htmlCandidate string
  19. if imageCandidate.Descriptor != "" {
  20. htmlCandidate = fmt.Sprintf(`%s %s`, imageCandidate.ImageURL, imageCandidate.Descriptor)
  21. } else {
  22. htmlCandidate = imageCandidate.ImageURL
  23. }
  24. htmlCandidates = append(htmlCandidates, htmlCandidate)
  25. }
  26. return strings.Join(htmlCandidates, ", ")
  27. }
  28. // ParseSrcSetAttribute returns the list of image candidates from the set.
  29. // https://html.spec.whatwg.org/#parse-a-srcset-attribute
  30. func ParseSrcSetAttribute(attributeValue string) (imageCandidates ImageCandidates) {
  31. unparsedCandidates := strings.Split(attributeValue, ", ")
  32. for _, unparsedCandidate := range unparsedCandidates {
  33. if candidate, err := parseImageCandidate(unparsedCandidate); err == nil {
  34. imageCandidates = append(imageCandidates, candidate)
  35. }
  36. }
  37. return imageCandidates
  38. }
  39. func parseImageCandidate(input string) (*ImageCandidate, error) {
  40. input = strings.TrimSpace(input)
  41. parts := strings.Split(strings.TrimSpace(input), " ")
  42. nbParts := len(parts)
  43. if nbParts > 2 || nbParts == 0 {
  44. return nil, fmt.Errorf(`srcset: invalid number of descriptors`)
  45. }
  46. if nbParts == 2 {
  47. if !isValidWidthOrDensityDescriptor(parts[1]) {
  48. return nil, fmt.Errorf(`srcset: invalid descriptor`)
  49. }
  50. return &ImageCandidate{ImageURL: parts[0], Descriptor: parts[1]}, nil
  51. }
  52. return &ImageCandidate{ImageURL: parts[0]}, nil
  53. }
  54. func isValidWidthOrDensityDescriptor(value string) bool {
  55. if value == "" {
  56. return false
  57. }
  58. lastChar := value[len(value)-1:]
  59. if lastChar != "w" && lastChar != "x" {
  60. return false
  61. }
  62. _, err := strconv.ParseFloat(value[0:len(value)-1], 32)
  63. return err == nil
  64. }