Textová data se v JavaScriptu ukládajà jako ÅetÄzce. Neexistuje zvláštnà typ pro jediný znak.
Internà formát ÅetÄzce je vždy UTF-16, nezávisle na kódovánà stránky.
Uvozovky
PÅipomeÅme si druhy uvozovek.
ÅetÄzce mohou být uzavÅeny do jednoduchých, dvojitých nebo zpÄtných uvozovek:
let jednoduché = 'jednoduché uvozovky';
let dvojité = "dvojité uvozovky";
let zpÄtné = `zpÄtné uvozovky`;
Jednoduché a dvojité uvozovky fungujà v zásadÄ stejnÄ. ZpÄtné uvozovky nám vÅ¡ak umožÅujà vložit do ÅetÄzce jakýkoli výraz, když jej uzavÅeme do ${â¦}:
function souÄet(a, b) {
return a + b;
}
alert(`1 + 2 = ${souÄet(1, 2)}.`); // 1 + 2 = 3.
Dalšà výhodou použÃvánà zpÄtných uvozovek je, že umožÅujà rozdÄlit ÅetÄzec na vÃce Åádků:
let seznamHostů = `Hosté:
* Jan
* Petr
* Marie
`;
alert(seznamHostů); // seznam hostů, vÃce Åádků
Vypadá to pÅirozenÄ, že? Ale jednoduché nebo dvojité uvozovky takto nefungujÃ.
Jestliže je použijeme a pokusÃme se rozdÄlit text na vÃce Åádků, nastane chyba:
let seznamHostů = "Hosté: // Error: Unexpected token ILLEGAL
* Jan";
Jednoduché a dvojité uvozovky pocházejà ze starých Äasů vzniku jazyka, kdy nebyla brána v úvahu potÅeba vÃceÅádkových ÅetÄzců. ZpÄtné uvozovky se objevily mnohem pozdÄji, a tak jsou univerzálnÄjÅ¡Ã.
ZpÄtné uvozovky nám také umožÅujà specifikovat âÅ¡ablonovou funkciâ pÅed levými uvozovkami. Syntaxe je: funkce`ÅetÄzec`. Funkce funkce je volána automaticky, obdržà ÅetÄzec a vnoÅené výrazy a může je zpracovat. Tato vlastnost se nazývá âznaÄkované Å¡ablonyâ (anglicky âtagged templatesâ). Je k vidÄnà jen zÅÃdka, ale můžete si o nà pÅeÄÃst v MDN: Å ablonové literály.
Speciálnà znaky
Je ovÅ¡em možné vytvoÅit vÃceÅádkové ÅetÄzce uzavÅené do jednoduchých nebo dvojitých uvozovek pomocà tzv. âznaku nového Åádkuâ, který se zapisuje \n a stanovuje konec Åádku:
let seznamHostů = "Hosté:\n * Jan\n * Petr\n * Marie";
alert(seznamHostů); // vÃceÅádkový seznam hostů, stejný jako výše
Jednoduššà pÅÃklad: tyto dva Åádky majà stejný význam, jen jsou různÄ zapsané:
let ÅetÄzec1 = "Ahoj\nsvÄte"; // dva Åádky pomocà âsymbolu konce Åádkuâ
// dva Åádky pomocà obyÄejného nového Åádku a zpÄtných uvozovek
let ÅetÄzec2 = `Ahoj
svÄte`;
alert(ÅetÄzec1 == ÅetÄzec2); // true
Existujà i jiné, ménÄ bÄžné speciálnà znaky:
| Znak | Popis |
|---|---|
\n |
Nový Åádek |
\r |
V textových souborech ve Windows reprezentuje konec Åádku kombinace dvou znaků \r\n, zatÃmco v jiných OS je to pouze \n. Je to z historických důvodů, vÄtÅ¡ina softwaru pod Windows rozumà i \n. |
\', \", \` |
Uvozovky |
\\ |
ZpÄtné lomÃtko |
\t |
Tabulátor |
\b, \f, \v |
Backspace, Form Feed, vertikálnà tabulátor â uvedeny jen pro úplnost, pocházejà z dÅÃvÄjšà doby, v souÄasnosti se nepoužÃvajà (můžete na nÄ rovnou zapomenout). |
Jak vidÃte, vÅ¡echny speciálnà znaky zaÄÃnajà zpÄtným lomÃtkem \, které se také nazývá âúnikový znakâ.
Protože je tak speciálnÃ, musÃme je zdvojit, jestliže chceme v ÅetÄzci zobrazit skuteÄné zpÄtné lomÃtko \:
alert( `ZpÄtné lomÃtko: \\` ); // ZpÄtné lomÃtko: \
K vloženà uvozovek do ÅetÄzce, který je ohraniÄen stejným druhem uvozovek, se použÃvajà tzv. âúnikovanéâ uvozovky \', \", \`.
NapÅÃklad:
alert( 'To\'s pÅehnal!' ); // To's pÅehnal!
Jak vidÃme, museli jsme pÅed vnitÅnÃmi jednoduchými uvozovkami uvést zpÄtné lomÃtko \', jinak by tyto uvozovky znamenaly konec ÅetÄzce.
SamozÅejmÄ musÃme pÅedznamenat únikovým znakem jen stejný druh uvozovek jako ty, které obklopujà ÅetÄzec. Jako elegantnÄjšà ÅeÅ¡enà bychom tedy mohli použÃt dvojité nebo zpÄtné uvozovky:
alert( `To's pÅehnal!` ); // To's pÅehnal!
KromÄ tÄchto speciálnÃch znaků existuje i speciálnà zápis pro kódy Unicode \uâ¦. PoužÃvá se jen málokdy a je vysvÄtlen v pokroÄilejšà kapitole o Unicode.
Délka ÅetÄzce
Délku ÅetÄzce obsahuje vlastnost length:
alert( `Já\n`.length ); // 3
VÅ¡imnÄte si, že \n je jediný âspeciálnÃâ znak, takže délka bude opravdu 3.
length je vlastnostLidé zvyklà na nÄkteré jiné jazyky nÄkdy nesprávnÄ pÚà volánà funkce ÅetÄzec.length() mÃsto ÅetÄzec.length. To nefunguje.
ProsÃme vÅ¡imnÄte si, že ÅetÄzec.length je ÄÃselná vlastnost, ne funkce. Nenà důvod za nà uvádÄt závorky. NepÃÅ¡e se .length(), ale .length.
PÅÃstup ke znakům
Abyste zÃskali znak na pozici poz, použijte hranaté závorky [poz] nebo zavolejte metodu ÅetÄzec.at(poz). Prvnà znak se nacházà na pozici nula:
let ÅetÄzec = `Ahoj`;
// prvnà znak
alert( ÅetÄzec[0] ); // A
alert( ÅetÄzec.at(0) ); // A
// poslednà znak
alert( ÅetÄzec[ÅetÄzec.length - 1] ); // j
alert( ÅetÄzec.at(-1) );
Jak vidÃte, metoda .at(poz) má výhodu v tom, že umožÅuje zadat zápornou pozici. Je-li poz záporná, poÄÃtá se od konce ÅetÄzce.
Takže .at(-1) znamená poslednà znak, .at(-2) je znak pÅed nÃm atd.
Hranaté závorky se záporným indexem vrátà vždy undefined, napÅÃklad:
let ÅetÄzec = `Ahoj`;
alert( ÅetÄzec[-2] ); // undefined
alert( ÅetÄzec.at(-2) ); // o
Můžeme také procházet jednotlivé znaky pomocà for..of:
for (let znak of "Ahoj") {
alert(znak); // A,h,o,j (znak bude "A", pak "h", pak "o" atd.)
}
ÅetÄzce jsou nemÄnné
ÅetÄzce v JavaScriptu nelze mÄnit. Nenà možné v nich zmÄnit nÄkterý znak.
Zkusme to, abychom vidÄli, že to nefunguje:
let ÅetÄzec = 'Ahoj';
ÅetÄzec[0] = 'a'; // chyba
alert( ÅetÄzec[0] ); // nefunguje to
Obvyklý způsob, jak to obejÃt, je vytvoÅit úplnÄ nový ÅetÄzec a pÅiÅadit jej do promÄnné ÅetÄzec namÃsto starého.
NapÅÃklad:
let ÅetÄzec = 'Pa';
ÅetÄzec = 'p' + ÅetÄzec[1]; // nahradà ÅetÄzec
alert( ÅetÄzec ); // pa
V následujÃcÃch podkapitolách uvidÃme dalšà pÅÃklady.
ZmÄna pÃsmen na malá nebo velká
Metoda toLowerCase() mÄnà pÃsmena ÅetÄzce na malá a metoda toUpperCase() na velká:
alert( 'RozhranÃ'.toUpperCase() ); // ROZHRANÃ
alert( 'RozhranÃ'.toLowerCase() ); // rozhranÃ
Nebo pokud chceme jediný znak, zmÄnÄný na malé pÃsmeno:
alert( 'RozhranÃ'[0].toLowerCase() ); // 'r'
Hledánà podÅetÄzce
Je mnoho způsobů, jak v ÅetÄzci najÃt podÅetÄzec.
ÅetÄzec.indexOf
Prvnà metoda je ÅetÄzec.indexOf(podÅetÄzec, pozice).
Hledá podÅetÄzec v ÅetÄzec, poÄÃnajÃc zadanou pozicà pozice, a vrátà pozici, na nÞ byla nalezena shoda. Jestliže podÅetÄzec nebyl nalezen, vrátà -1.
NapÅÃklad:
let ÅetÄzec = 'Prorokovo oko';
alert( ÅetÄzec.indexOf('Prorokovo') ); // 0, protože 'Prorokovo' je nalezen na zaÄátku
alert( ÅetÄzec.indexOf('prorokovo') ); // -1, nenalezeno, hledánà rozliÅ¡uje malá a velká pÃsmena
alert( ÅetÄzec.indexOf("oko") ); // 4, "oko" nalezeno na pozici 4 (..okovo oko)
Nepovinný druhý parametr nám umožÅuje zahájit hledánà na zadané pozici.
NapÅÃklad prvnà výskyt "oko" je na pozici 4. Chceme-li hledat dalšà výskyt, zaÄnÄme hledánà od pozice 5:
let ÅetÄzec = 'Prorokovo oko';
alert( ÅetÄzec.indexOf('oko', 5) ) // 10
Pokud nás zajÃmajà vÅ¡echny výskyty, můžeme spustit indexOf v cyklu. Každé nové volánà bude zaÄÃnat na pozici za pÅedchozÃm nálezem:
let ÅetÄzec = 'Kdyby byly v Åece ryby, nebylo by tÅeba rybnÃka';
let cÃl = 'by'; // hledejme
let poz = 0;
while (true) {
let nalezenáPozice = ÅetÄzec.indexOf(cÃl, poz);
if (nalezenáPozice == -1) break;
alert( `Nalezeno na ${nalezenáPozice}` );
poz = nalezenáPozice + 1; // pokraÄujeme v hledánà od dalšà pozice
}
Stejný algoritmus lze napsat kratÅ¡Ãm způsobem:
let ÅetÄzec = "Kdyby byly v Åece ryby, nebylo by tÅeba rybnÃka";
let cÃl = "by";
let poz = -1;
while ((poz = ÅetÄzec.indexOf(cÃl, poz + 1)) != -1) {
alert( poz );
}
ÅetÄzec.lastIndexOf(podÅetÄzec, pozice)Existuje i podobná metoda ÅetÄzec.lastIndexOf(podÅetÄzec, pozice), která hledá od konce ÅetÄzce smÄrem k jeho zaÄátku.
Ta by vypsala výskyty v opaÄném poÅadÃ.
Metoda indexOf vnášà do podmÃnky v if drobnou nepÅÃjemnost. Nemůžeme ji umÃstit do if takto:
let ÅetÄzec = "Prorokovo oko";
if (ÅetÄzec.indexOf("Prorokovo")) {
alert("Našli jsme"); // to nefunguje!
}
V uvedeném pÅÃkladu se alert nezobrazÃ, protože metoda ÅetÄzec.indexOf("Prorokovo") vrátila 0 (což znamená, že naÅ¡la shodu na poÄáteÄnà pozici). To je správnÄ, ale if považuje 0 za false.
SprávnÄ bychom tedy mÄli porovnávat s -1, napÅ. takto:
let ÅetÄzec = "Prorokovo oko";
if (ÅetÄzec.indexOf("Prorokovo") != -1) {
alert("NaÅ¡li jsme"); // teÄ to funguje!
}
includes, startsWith, endsWith
ModernÄjšà metoda ÅetÄzec.includes(podÅetÄzec, poz) vrátà true/false podle toho, zda ÅetÄzec v sobÄ obsahuje podÅetÄzec.
Je to ta pravá možnost, když potÅebujeme testovat výskyt, ale nezajÃmá nás jeho pozice:
alert( "Prorokovo oko".includes("Prorok") ); // true
alert( "Ahoj".includes("Sbohem") ); // false
Nepovinný druhý argument ÅetÄzec.includes je pozice, od nÞ se má zaÄÃt hledat:
alert( "Prorokovo".includes("oko") ); // true
alert( "Prorokovo".includes("oko", 5) ); // false, od pozice 5 nenà žádné "oko"
Metody ÅetÄzec.startsWith a ÅetÄzec.endsWith dÄlajà pÅesnÄ to, co je jejich názvem (zjistÃ, zda ÅetÄzec zaÄÃná âstartsWithâ nebo konÄà âendsWithâ zadaným podÅetÄzcem â pozn. pÅekl.):
alert( "Prorok".startsWith("Pro") ); // true, "Prorok" zaÄÃná na "Pro"
alert( "Prorok".endsWith("rok") ); // true, "Prorok" konÄÃ na "rok"
ZÃskánà podÅetÄzce
V JavaScriptu jsou 3 metody pro zÃskánà podÅetÄzce: substring, substr a slice.
ÅetÄzec.slice(zaÄátek [, konec])-
Vrátà Äást ÅetÄzce od pozice
zaÄátekdo (ale ne vÄetnÄ) pozicekonec.NapÅÃklad:
let ÅetÄzec = "ÅetÄzenÃ"; alert( ÅetÄzec.slice(0, 5) ); // 'ÅetÄz', podÅetÄzec od 0 do 5 (mimo 5) alert( ÅetÄzec.slice(0, 1) ); // 'Å', od 0 do 1, ale mimo 1, takže jediný znak na pozici 0NenÃ-li uveden druhý argument,
slicevrátà podÅetÄzec až do konce ÅetÄzce:let ÅetÄzec = "ÅetÄzenÃ"; alert( ÅetÄzec.slice(2) ); // 'tÄzenÃ', od 2. pozice do konceJe možné, aby
zaÄátek/konecmÄly záporné hodnoty. Ty znamenajÃ, že pozice se poÄÃtá od konce ÅetÄzce:let ÅetÄzec = "ÅetÄzenÃ"; // zaÄátek na 4. pozici zprava, konec na 1. pozici zprava alert( ÅetÄzec.slice(-4, -1) ); // 'zen' ÅetÄzec.substring(zaÄátek [, konec])-
Vrátà Äást ÅetÄzce mezi pozicemi
zaÄátekakonec(nezahrnujekonec).Je to témÄÅ totéž jako
slice, ale umožÅuje, abyzaÄátekbyl vÄtšà nežkonec(v takovém pÅÃpadÄ se hodnotyzaÄátekakonecjednoduÅ¡e prohodÃ).NapÅÃklad:
let ÅetÄzec = "ÅetÄzenÃ"; // tyto hodnoty jsou totéž pro substring: alert( ÅetÄzec.substring(2, 6) ); // "tÄze" alert( ÅetÄzec.substring(6, 2) ); // "tÄze" // ...ale ne pro slice: alert( ÅetÄzec.slice(2, 6) ); // "tÄze" (totéž) alert( ÅetÄzec.slice(6, 2) ); // "" (prázdný ÅetÄzec)Záporné argumenty (na rozdÃl od
slice) nejsou podporovány a zacházà se s nimi jako s0. ÅetÄzec.substr(zaÄátek [, délka])-
Vrátà Äást ÅetÄzce od pozice
zaÄátekse zadanou délkoudélka.Na rozdÃl od pÅedchozÃch metod nám tato umožÅuje specifikovat délku namÃsto koncové pozice:
let ÅetÄzec = "ÅetÄzenÃ"; alert( ÅetÄzec.substr(2, 4) ); // 'tÄze', od 2. pozice vezme 4 znakyPrvnà argument může být záporný, pak se bude poÄÃtat od konce:
let ÅetÄzec = "ÅetÄzenÃ"; alert( ÅetÄzec.substr(-4, 2) ); // 'ze', od 4. pozice zprava vezme 2 znaky
Tato metoda je obsažena v Dodatku B specifikace jazyka. Znamená to, že by ji mÄly podporovat jedinÄ motory JavaScriptu uvnitÅ prohlÞeÄů a nedoporuÄuje se ji použÃvat. V praxi je vÅ¡ak podporována vÅ¡ude.
Abychom pÅedeÅ¡li zmatkům, vÅ¡echny tyto metody si zrekapitulujme:
| metoda | vybÃrá⦠| záporné hodnoty |
|---|---|---|
slice(zaÄátek, konec) |
od zaÄátek do konec (mimo konec) |
umožÅuje záporné hodnoty |
substring(zaÄátek, konec) |
mezi zaÄátek a konec (mimo konec) |
záporné hodnoty znamenajà 0 |
substr(zaÄátek, délka) |
od zaÄátek vezme délka znaků |
umožÅuje záporný zaÄátek |
PotÅebnou práci odvedou vÅ¡echny. FormálnÄ má substr drobnou nevýhodu: nenà popsána v jádru specifikace JavaScriptu, ale v Dodatku B, který pokrývá vlastnosti fungujÃcà jen v prohlÞeÄÃch a existujÃcà zejména z historických důvodů. ProstÅedà mimo prohlÞeÄe ji tedy nemusejà podporovat. V praxi vÅ¡ak funguje vÅ¡ude.
Ze zbývajÃcÃch dvou variant je slice trochu flexibilnÄjÅ¡Ã, protože umožÅuje záporné hodnoty a je kratšà na napsánÃ.
Pro praktické použità si tedy staÄà pamatovat jen slice.
Porovnávánà ÅetÄzců
Jak vÃme z kapitoly PorovnávánÃ, ÅetÄzce se porovnávajà znak po znaku v abecednÃm poÅadÃ.
Existujà vÅ¡ak nÄkteré zvláštnosti.
-
Malé pÃsmeno je vždy vÄtšà než velké:
alert( 'a' > 'Z' ); // true -
PÃsmena s diakritickými znaménky jsou âmimo poÅadÃâ:
alert( 'Ãrán' > 'Zéland' ); // trueTo může vést ke zvláštnÃm výsledkům, budeme-li Åadit tyto názvy zemÃ. Obvykle se oÄekává, že
Zélandbude v seznamu až zaÃrán.
Abychom pochopili, co se tady dÄje, mÄli bychom vÄdÄt, že vÅ¡echny ÅetÄzce jsou zakódovány pomocà UTF-16. To znamená, že každý znak má odpovÃdajÃcà ÄÃselný kód.
Existujà speciálnà metody, které umožÅujà zÃskat znak pro zadaný kód a naopak:
ÅetÄzec.codePointAt(poz)-
Vrátà desÃtkové ÄÃslo pÅedstavujÃcà kód znaku na pozici
poz:// malá a velká pÃsmena majà různé kódy alert( "Z".codePointAt(0) ); // 90 alert( "z".codePointAt(0) ); // 122 alert( "z".codePointAt(0).toString(16) ); // 7a (potÅebujeme-li hexadecimálnà hodnotu) String.fromCodePoint(kód)-
VytvoÅà znak podle jeho ÄÃselného kódu
kód:alert( String.fromCodePoint(90) ); // Z alert( String.fromCodePoint(0x5a) ); // Z (jako argument můžeme použÃt i hexadecimálnà hodnotu)
Nynà se podÃvejme na znaky s kódy 65..220 (latinská abeceda a nÄco navÃc), když z nich vytvoÅÃme ÅetÄzec:
let ÅetÄzec = '';
for (let i = 65; i <= 220; i++) {
ÅetÄzec += String.fromCodePoint(i);
}
alert( ÅetÄzec );
// Výstup:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÂÂÂÂÂ
// ¡¢£¤¥¦§¨©ª«¬Â®¯°±²³´µ¶·¸¹º»¼½¾¿ÃÃÃÃÃÃ
ÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃ
VidÃte? NapÅed jsou velká pÃsmena, pak nÄkolik speciálnÃch znaků, pak malá pÃsmena a à je poblÞ konce výstupu.
Nynà bude zÅejmé, proÄ a > Z.
Znaky se porovnávajà podle svého ÄÃselného kódu. Vyššà kód znamená, že znak je vÄtÅ¡Ã. Kód a (97) je vyššà než kód Z (90).
- VÅ¡echna malá pÃsmena jsou až za velkými pÃsmeny, protože jejich kódy jsou vyššÃ.
- NÄkterá pÃsmena, napÅ.
Ã, stojà mimo hlavnà abecedu. Jejich kód je zde vyššà než kód kteréhokoli pÃsmene odadoz.
Správné porovnánÃ
âSprávnýâ algoritmus pro porovnánà ÅetÄzců je složitÄjÅ¡Ã, než se může zdát, protože různé jazyky majà různé abecedy.
ProhlÞeÄ tedy musà znát jazyk, v nÄmž porovnává.
NaÅ¡tÄstà modernà prohlÞeÄe podporujà internacionalizaÄnà standard ECMA-402.
Ten poskytuje speciálnà metodu, jak porovnávat ÅetÄzce v různých jazycÃch podle jejich pravidel.
Volánà ÅetÄzec.localeCompare(ÅetÄzec2) vrátà celé ÄÃslo, které oznamuje, zda ÅetÄzec je menÅ¡Ã, roven nebo vÄtšà než ÅetÄzec2 podle pravidel jazyka:
- Je-li
ÅetÄzecmenšà nežÅetÄzec2, vrátà záporné ÄÃslo. - Je-li
ÅetÄzecvÄtšà nežÅetÄzec2, vrátà kladné ÄÃslo. - Jsou-li si rovny, vrátÃ
0.
NapÅÃklad:
alert( 'Äesko'.localeCompare('Zéland') ); // -1
Tato metoda má ve skuteÄnosti jeÅ¡tÄ dva dalšà argumenty specifikované v dokumentaci, které umožÅujà specifikovat jazyk (standardnÄ se vezme z prostÅedÃ, na jazyku závisà poÅadà pÃsmen) a nastavit dalšà pravidla, napÅ. rozliÅ¡ovánà malých a velkých pÃsmen, nebo zda se "a" a "aÌ" majà považovat za stejné znaky, atd.
ShrnutÃ
- Existujà 3 druhy uvozovek. ZpÄtné uvozovky umožÅujà rozdÄlit ÅetÄzec na vÃce Åádků a vnoÅit výrazy
${â¦}. - Můžeme použÃvat speciálnà znaky, napÅÃklad konec Åádku
\n. - Chceme-li zÃskat znak, použijeme
[]nebo metoduat. - Chceme-li zÃskat podÅetÄzec, použijeme
slicenebosubstring. - Chceme-li pÅevést ÅetÄzec na malá/velká pÃsmena, použijeme
toLowerCase/toUpperCase. - Chceme-li najÃt podÅetÄzec, použijeme
indexOfnebo pro jednoduché kontrolyincludes/startsWith/endsWith. - Chceme-li porovnat ÅetÄzce podle jazykových pravidel, použijeme
localeCompare, jinak se budou porovnávat podle kódů znaků.
Pro ÅetÄzce existuje i nÄkolik dalÅ¡Ãch užiteÄných metod:
ÅetÄzec.trim()â odstranà mezery ze zaÄátku a konce ÅetÄzce.ÅetÄzec.repeat(n)â zopakuje ÅetÄzecn-krát.- â¦a dalšà lze najÃt v manuálu.
ÅetÄzce majà také metody pro hledánà a nahrazovánà pomocà regulárnÃch výrazů. To je vÅ¡ak rozsáhlé téma, proto je vysvÄtleno v samostatné Äásti tutoriálu Regulárnà výrazy.
Nynà je také důležité vÄdÄt, že ÅetÄzce jsou založeny na kódovánà Unicode, a proto jsou zde urÄité problémy s porovnávánÃm. O Unicode se dozvÃte vÃc v kapitole Unicode, vnitÅnà reprezentace ÅetÄzce.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)