diff --git a/DESCRIPTION b/DESCRIPTION index 4fc3450..b741d57 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -15,10 +15,10 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 Imports: htmltools, - rlang + jsonlite, + stringi Suggests: shiny, shinydashboard, - slugify, testthat (>= 3.0.0) Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index b0918d1..623cc18 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,3 +15,4 @@ export(htmlRemoveClasses) export(htmlReplaceBoxTitleLevel) export(htmlSetMaxLength) export(htmlSetMinLength) +export(htmlSlugify) diff --git a/R/htmlBoxManipulation.R b/R/htmlBoxManipulation.R index 13af5ba..80aec03 100644 --- a/R/htmlBoxManipulation.R +++ b/R/htmlBoxManipulation.R @@ -89,9 +89,7 @@ htmlFixBoxCollapseButtonAria <- function(box, context = NULL) { #' Adds a "help" (question mark) icon to a box tools section (right side) #' #' The uri to load can have a replacement marker supplied '%box%' which will be -#' substituded with a sanitized copy of the box title. Preference is given to -#' the slugify package, however if unavailable a warning is generated and the -#' fallback of URLEncode is used. +#' substituded with a sanitized copy of the box title. #' #' @param box The box to add a help icon/link to #' @param href The help uri to load @@ -126,17 +124,7 @@ htmlAddBoxHelpLink <- function(box, href, title = NULL) { if (is.null(title)) { title <- box_title } - - if (requireNamespace("slugify")) { - box_title <- slugify::slugify(box_title) - } else { - rlang::warn( - "slugify is not installed, resorting to URLencode", - .frequency = "regularly", - .frequency_id = "htmlAddBoxHelpLink::slugify" - ) - box_title <- utils::URLencode(box_title) - } + box_title <- htmlSlugify(box_title) htmltools::tagInsertChildren( box, diff --git a/R/htmlSlugify.R b/R/htmlSlugify.R new file mode 100644 index 0000000..4feea32 --- /dev/null +++ b/R/htmlSlugify.R @@ -0,0 +1,46 @@ +slugify_charmap_env <- new.env() + +#' Native(ish) R slugify +#' +#' @param x String to slugify +#' @param repl What to replace spaces with (default: -) +#' @param lower Lower case the output string +#' +#' @return The slugified output string +#' @export +#' +#' @examples +#' x <- c( +#' "A very short example", +#' "This has a $ symbol", +#' "user@example.com", +#' "~~ABC123~~", +#' "τυχαίος", +#' "file:///tmp/some/path" +#' ) +#' htmlSlugify(x) +#' htmlSlugify(x, "_") +htmlSlugify <- function(x, repl = "-", lower = TRUE) +{ + if (!exists("slugify_charmap", envir = slugify_charmap_env)){ + assign( + "slugify_charmap", + jsonlite::fromJSON( + system.file("slugify_charmap.json", package = "AVSDevR.HTMLUtils") + ), + envir = slugify_charmap_env + ) + } + slugify_charmap <- get("slugify_charmap", envir = slugify_charmap_env) + + x <- stringi::stri_replace_all_fixed( + x, names(slugify_charmap), slugify_charmap, vectorize_all = FALSE + ) + x <- stringi::stri_replace_all_regex(x, "[^\\P{P}-]", "") + x <- stringi::stri_trim_both(x) + x <- stringi::stri_replace_all_regex(x, "[[:space:]]+", repl) + if (lower) { + x <- stringi::stri_trans_tolower(x) + } + x +} diff --git a/inst/slugify_charmap.json b/inst/slugify_charmap.json new file mode 100644 index 0000000..f38c8e4 --- /dev/null +++ b/inst/slugify_charmap.json @@ -0,0 +1,643 @@ +{ + "$": "dollar", + "%": "percent", + "&": "and", + "<": "less", + ">": "greater", + "|": "or", + "¢": "cent", + "£": "pound", + "¤": "currency", + "¥": "yen", + "©": "(c)", + "ª": "a", + "®": "(r)", + "º": "o", + "À": "A", + "Á": "A", + "Â": "A", + "Ã": "A", + "Ä": "A", + "Å": "A", + "Æ": "AE", + "Ç": "C", + "È": "E", + "É": "E", + "Ê": "E", + "Ë": "E", + "Ì": "I", + "Í": "I", + "Î": "I", + "Ï": "I", + "Ð": "D", + "Ñ": "N", + "Ò": "O", + "Ó": "O", + "Ô": "O", + "Õ": "O", + "Ö": "O", + "Ø": "O", + "Ù": "U", + "Ú": "U", + "Û": "U", + "Ü": "U", + "Ý": "Y", + "Þ": "TH", + "ß": "ss", + "à": "a", + "á": "a", + "â": "a", + "ã": "a", + "ä": "a", + "å": "a", + "æ": "ae", + "ç": "c", + "è": "e", + "é": "e", + "ê": "e", + "ë": "e", + "ì": "i", + "í": "i", + "î": "i", + "ï": "i", + "ð": "d", + "ñ": "n", + "ò": "o", + "ó": "o", + "ô": "o", + "õ": "o", + "ö": "o", + "ø": "o", + "ù": "u", + "ú": "u", + "û": "u", + "ü": "u", + "ý": "y", + "þ": "th", + "ÿ": "y", + "Ā": "A", + "ā": "a", + "Ă": "A", + "ă": "a", + "Ą": "A", + "ą": "a", + "Ć": "C", + "ć": "c", + "Č": "C", + "č": "c", + "Ď": "D", + "ď": "d", + "Đ": "DJ", + "đ": "dj", + "Ē": "E", + "ē": "e", + "Ė": "E", + "ė": "e", + "Ę": "e", + "ę": "e", + "Ě": "E", + "ě": "e", + "Ğ": "G", + "ğ": "g", + "Ģ": "G", + "ģ": "g", + "Ĩ": "I", + "ĩ": "i", + "Ī": "i", + "ī": "i", + "Į": "I", + "į": "i", + "İ": "I", + "ı": "i", + "Ķ": "k", + "ķ": "k", + "Ļ": "L", + "ļ": "l", + "Ľ": "L", + "ľ": "l", + "Ł": "L", + "ł": "l", + "Ń": "N", + "ń": "n", + "Ņ": "N", + "ņ": "n", + "Ň": "N", + "ň": "n", + "Ō": "O", + "ō": "o", + "Ő": "O", + "ő": "o", + "Œ": "OE", + "œ": "oe", + "Ŕ": "R", + "ŕ": "r", + "Ř": "R", + "ř": "r", + "Ś": "S", + "ś": "s", + "Ş": "S", + "ş": "s", + "Š": "S", + "š": "s", + "Ţ": "T", + "ţ": "t", + "Ť": "T", + "ť": "t", + "Ũ": "U", + "ũ": "u", + "Ū": "u", + "ū": "u", + "Ů": "U", + "ů": "u", + "Ű": "U", + "ű": "u", + "Ų": "U", + "ų": "u", + "Ŵ": "W", + "ŵ": "w", + "Ŷ": "Y", + "ŷ": "y", + "Ÿ": "Y", + "Ź": "Z", + "ź": "z", + "Ż": "Z", + "ż": "z", + "Ž": "Z", + "ž": "z", + "Ə": "E", + "ƒ": "f", + "Ơ": "O", + "ơ": "o", + "Ư": "U", + "ư": "u", + "Lj": "LJ", + "lj": "lj", + "Nj": "NJ", + "nj": "nj", + "Ș": "S", + "ș": "s", + "Ț": "T", + "ț": "t", + "ə": "e", + "˚": "o", + "Ά": "A", + "Έ": "E", + "Ή": "H", + "Ί": "I", + "Ό": "O", + "Ύ": "Y", + "Ώ": "W", + "ΐ": "i", + "Α": "A", + "Β": "B", + "Γ": "G", + "Δ": "D", + "Ε": "E", + "Ζ": "Z", + "Η": "H", + "Θ": "8", + "Ι": "I", + "Κ": "K", + "Λ": "L", + "Μ": "M", + "Ν": "N", + "Ξ": "3", + "Ο": "O", + "Π": "P", + "Ρ": "R", + "Σ": "S", + "Τ": "T", + "Υ": "Y", + "Φ": "F", + "Χ": "X", + "Ψ": "PS", + "Ω": "W", + "Ϊ": "I", + "Ϋ": "Y", + "ά": "a", + "έ": "e", + "ή": "h", + "ί": "i", + "ΰ": "y", + "α": "a", + "β": "b", + "γ": "g", + "δ": "d", + "ε": "e", + "ζ": "z", + "η": "h", + "θ": "8", + "ι": "i", + "κ": "k", + "λ": "l", + "μ": "m", + "ν": "n", + "ξ": "3", + "ο": "o", + "π": "p", + "ρ": "r", + "ς": "s", + "σ": "s", + "τ": "t", + "υ": "y", + "φ": "f", + "χ": "x", + "ψ": "ps", + "ω": "w", + "ϊ": "i", + "ϋ": "y", + "ό": "o", + "ύ": "y", + "ώ": "w", + "Ё": "Yo", + "Ђ": "DJ", + "Є": "Ye", + "І": "I", + "Ї": "Yi", + "Ј": "J", + "Љ": "LJ", + "Њ": "NJ", + "Ћ": "C", + "Џ": "DZ", + "А": "A", + "Б": "B", + "В": "V", + "Г": "G", + "Д": "D", + "Е": "E", + "Ж": "Zh", + "З": "Z", + "И": "I", + "Й": "J", + "К": "K", + "Л": "L", + "М": "M", + "Н": "N", + "О": "O", + "П": "P", + "Р": "R", + "С": "S", + "Т": "T", + "У": "U", + "Ф": "F", + "Х": "H", + "Ц": "C", + "Ч": "Ch", + "Ш": "Sh", + "Щ": "Sh", + "Ъ": "U", + "Ы": "Y", + "Ь": "", + "Э": "E", + "Ю": "Yu", + "Я": "Ya", + "а": "a", + "б": "b", + "в": "v", + "г": "g", + "д": "d", + "е": "e", + "ж": "zh", + "з": "z", + "и": "i", + "й": "j", + "к": "k", + "л": "l", + "м": "m", + "н": "n", + "о": "o", + "п": "p", + "р": "r", + "с": "s", + "т": "t", + "у": "u", + "ф": "f", + "х": "h", + "ц": "c", + "ч": "ch", + "ш": "sh", + "щ": "sh", + "ъ": "u", + "ы": "y", + "ь": "", + "э": "e", + "ю": "yu", + "я": "ya", + "ё": "yo", + "ђ": "dj", + "є": "ye", + "і": "i", + "ї": "yi", + "ј": "j", + "љ": "lj", + "њ": "nj", + "ћ": "c", + "ѝ": "u", + "џ": "dz", + "Ґ": "G", + "ґ": "g", + "Ғ": "GH", + "ғ": "gh", + "Қ": "KH", + "қ": "kh", + "Ң": "NG", + "ң": "ng", + "Ү": "UE", + "ү": "ue", + "Ұ": "U", + "ұ": "u", + "Һ": "H", + "һ": "h", + "Ә": "AE", + "ә": "ae", + "Ө": "OE", + "ө": "oe", + "Ա": "A", + "Բ": "B", + "Գ": "G", + "Դ": "D", + "Ե": "E", + "Զ": "Z", + "Է": "E'", + "Ը": "Y'", + "Թ": "T'", + "Ժ": "JH", + "Ի": "I", + "Լ": "L", + "Խ": "X", + "Ծ": "C'", + "Կ": "K", + "Հ": "H", + "Ձ": "D'", + "Ղ": "GH", + "Ճ": "TW", + "Մ": "M", + "Յ": "Y", + "Ն": "N", + "Շ": "SH", + "Չ": "CH", + "Պ": "P", + "Ջ": "J", + "Ռ": "R'", + "Ս": "S", + "Վ": "V", + "Տ": "T", + "Ր": "R", + "Ց": "C", + "Փ": "P'", + "Ք": "Q'", + "Օ": "O''", + "Ֆ": "F", + "և": "EV", + "ء": "a", + "آ": "aa", + "أ": "a", + "ؤ": "u", + "إ": "i", + "ئ": "e", + "ا": "a", + "ب": "b", + "ة": "h", + "ت": "t", + "ث": "th", + "ج": "j", + "ح": "h", + "خ": "kh", + "د": "d", + "ذ": "th", + "ر": "r", + "ز": "z", + "س": "s", + "ش": "sh", + "ص": "s", + "ض": "dh", + "ط": "t", + "ظ": "z", + "ع": "a", + "غ": "gh", + "ف": "f", + "ق": "q", + "ك": "k", + "ل": "l", + "م": "m", + "ن": "n", + "ه": "h", + "و": "w", + "ى": "a", + "ي": "y", + "ً": "an", + "ٌ": "on", + "ٍ": "en", + "َ": "a", + "ُ": "u", + "ِ": "e", + "ْ": "", + "٠": "0", + "١": "1", + "٢": "2", + "٣": "3", + "٤": "4", + "٥": "5", + "٦": "6", + "٧": "7", + "٨": "8", + "٩": "9", + "پ": "p", + "چ": "ch", + "ژ": "zh", + "ک": "k", + "گ": "g", + "ی": "y", + "۰": "0", + "۱": "1", + "۲": "2", + "۳": "3", + "۴": "4", + "۵": "5", + "۶": "6", + "۷": "7", + "۸": "8", + "۹": "9", + "฿": "baht", + "ა": "a", + "ბ": "b", + "გ": "g", + "დ": "d", + "ე": "e", + "ვ": "v", + "ზ": "z", + "თ": "t", + "ი": "i", + "კ": "k", + "ლ": "l", + "მ": "m", + "ნ": "n", + "ო": "o", + "პ": "p", + "ჟ": "zh", + "რ": "r", + "ს": "s", + "ტ": "t", + "უ": "u", + "ფ": "f", + "ქ": "k", + "ღ": "gh", + "ყ": "q", + "შ": "sh", + "ჩ": "ch", + "ც": "ts", + "ძ": "dz", + "წ": "ts", + "ჭ": "ch", + "ხ": "kh", + "ჯ": "j", + "ჰ": "h", + "Ṣ": "S", + "ṣ": "s", + "Ẁ": "W", + "ẁ": "w", + "Ẃ": "W", + "ẃ": "w", + "Ẅ": "W", + "ẅ": "w", + "ẞ": "SS", + "Ạ": "A", + "ạ": "a", + "Ả": "A", + "ả": "a", + "Ấ": "A", + "ấ": "a", + "Ầ": "A", + "ầ": "a", + "Ẩ": "A", + "ẩ": "a", + "Ẫ": "A", + "ẫ": "a", + "Ậ": "A", + "ậ": "a", + "Ắ": "A", + "ắ": "a", + "Ằ": "A", + "ằ": "a", + "Ẳ": "A", + "ẳ": "a", + "Ẵ": "A", + "ẵ": "a", + "Ặ": "A", + "ặ": "a", + "Ẹ": "E", + "ẹ": "e", + "Ẻ": "E", + "ẻ": "e", + "Ẽ": "E", + "ẽ": "e", + "Ế": "E", + "ế": "e", + "Ề": "E", + "ề": "e", + "Ể": "E", + "ể": "e", + "Ễ": "E", + "ễ": "e", + "Ệ": "E", + "ệ": "e", + "Ỉ": "I", + "ỉ": "i", + "Ị": "I", + "ị": "i", + "Ọ": "O", + "ọ": "o", + "Ỏ": "O", + "ỏ": "o", + "Ố": "O", + "ố": "o", + "Ồ": "O", + "ồ": "o", + "Ổ": "O", + "ổ": "o", + "Ỗ": "O", + "ỗ": "o", + "Ộ": "O", + "ộ": "o", + "Ớ": "O", + "ớ": "o", + "Ờ": "O", + "ờ": "o", + "Ở": "O", + "ở": "o", + "Ỡ": "O", + "ỡ": "o", + "Ợ": "O", + "ợ": "o", + "Ụ": "U", + "ụ": "u", + "Ủ": "U", + "ủ": "u", + "Ứ": "U", + "ứ": "u", + "Ừ": "U", + "ừ": "u", + "Ử": "U", + "ử": "u", + "Ữ": "U", + "ữ": "u", + "Ự": "U", + "ự": "u", + "Ỳ": "Y", + "ỳ": "y", + "Ỵ": "Y", + "ỵ": "y", + "Ỷ": "Y", + "ỷ": "y", + "Ỹ": "Y", + "ỹ": "y", + "–": "-", + "‘": "'", + "’": "'", + "“": "\\\"", + "”": "\\\"", + "„": "\\\"", + "†": "+", + "•": "*", + "…": "...", + "₠": "ecu", + "₢": "cruzeiro", + "₣": "french franc", + "₤": "lira", + "₥": "mill", + "₦": "naira", + "₧": "peseta", + "₨": "rupee", + "₩": "won", + "₪": "new shequel", + "₫": "dong", + "€": "euro", + "₭": "kip", + "₮": "tugrik", + "₯": "drachma", + "₰": "penny", + "₱": "peso", + "₲": "guarani", + "₳": "austral", + "₴": "hryvnia", + "₵": "cedi", + "₸": "kazakhstani tenge", + "₹": "indian rupee", + "₺": "turkish lira", + "₽": "russian ruble", + "₿": "bitcoin", + "℠": "sm", + "™": "tm", + "∂": "d", + "∆": "delta", + "∑": "sum", + "∞": "infinity", + "♥": "love", + "元": "yuan", + "円": "yen", + "﷼": "rial", + "ﻵ": "laa", + "ﻷ": "laa", + "ﻹ": "lai", + "ﻻ": "la" +} \ No newline at end of file diff --git a/man/htmlAddBoxHelpLink.Rd b/man/htmlAddBoxHelpLink.Rd index 6512b0c..13f38c7 100644 --- a/man/htmlAddBoxHelpLink.Rd +++ b/man/htmlAddBoxHelpLink.Rd @@ -18,9 +18,7 @@ The modified box } \description{ The uri to load can have a replacement marker supplied '\%box\%' which will be -substituded with a sanitized copy of the box title. Preference is given to -the slugify package, however if unavailable a warning is generated and the -fallback of URLEncode is used. +substituded with a sanitized copy of the box title. } \examples{ x <- shinydashboard::box(title = "This is a box") diff --git a/man/htmlSlugify.Rd b/man/htmlSlugify.Rd new file mode 100644 index 0000000..d7e80b9 --- /dev/null +++ b/man/htmlSlugify.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/htmlSlugify.R +\name{htmlSlugify} +\alias{htmlSlugify} +\title{Native(ish) R slugify} +\usage{ +htmlSlugify(x, repl = "-", lower = TRUE) +} +\arguments{ +\item{x}{String to slugify} + +\item{repl}{What to replace spaces with (default: -)} + +\item{lower}{Lower case the output string} +} +\value{ +The slugified output string +} +\description{ +Native(ish) R slugify +} +\examples{ +x <- c( + "A very short example", + "This has a $ symbol", + "user@example.com", + "~~ABC123~~", + "τυχαίος", + "file:///tmp/some/path" +) +htmlSlugify(x) +htmlSlugify(x, "_") +}