Skriptujeme InDesign (17): Regulární výrazy - Grafika.cz - vše o počítačové grafice

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



skenerista, osvitář

Skriptujeme InDesign (17): Regulární výrazy

Adobe InDesign sazba

23. června 2006, 00.00 | Velmi silný, ale ne zrovna snadno použitelný prostředek. Tak nějak bychom mohli popsat
použití regulárních výrazů při skriptování v InDesignu. Více už v našem článku.

Jak jsme již přislíbili, v této části našeho seriálu se budeme věnovat regulárním výrazům, prostředkům umožňující výrazně zefektivnit zpracování textového obsahu v InDesignu pomocí skriptů. Nicméně jak se z našeho výkladu záhy ukáže, nejedná se o nástroj, který by byl osvojitelný během chvilky.

Co jsou regulární výrazy?

Pod pojmem regulární výraz se rozumí výraz - vzor, dovolující popsat vlastnost určitého textového řetězce. Na základě tohoto popisu je možno regulární výraz použít jak při hledání, tak i nahrazování textového obsahu (a samozřejmě i dalších transformacích či jiném využití nalezeného obsahu, v případě InDesignu je to typicky formátování nalezených řetězců). Oproti funkci pro hledání a nahrazení v InDesignu, kterou jsme si popsali v minulých dílech našeho seriálu, jsou regulární výrazy podstatně univerzálnějším prostředkem a dovolují zapsat a následně tedy i zpracovat výrazy, pro jejichž vyjádření nemá daná vyhledávací funkce adekvátní prostředky. Daní placenou za tuto možnost je ovšem potřeba naučit se ne právě jednoduchá pravidla pro tvorbu regulárních výrazů a také použít určité skriptovací konstrukce, neboť přímočaré použití ve stylu výše popsané vyhledávací funkce InDesignu možné není - podpora regulárních výrazů je zde pro obecný JavaScript a její nasazení v rámci skriptování InDesignu je tak trochu navíc.

Základní pravidla pro tvorbu regulárních výrazů

V JavaScriptu existují dva způsoby, jak definovat regulární výraz. První je založen na použití výrazu - literáru ve tvaru:

/regulární_výraz/modifikátor

Syntaxi pro regulární_výraz probereme dále, nepovinné modifikátory mohou být g (zpracovávají se všechny výskyty výrazu), i (vyhledává se bez rozlišování malých a velkých písmen) či m (zpracování se týká víceřádkového režimu při použití značek ^ a $ - viz další popis). Je-li použito modifikátorů více, mohou se psát vedle sebe bez ohledu na pořadí.

Pokud tedy ve skriptu napíšeme:

var myRegVyr=/abc/gi;

definujeme tím proměnnou myRegVyr, která obsahuje regulární výraz abc a je určena k použití globálně a bez rozlišování malých a velkých písmen při hledání shody.

V druhém případě používáme konstruktoru RegExp, vytvoření nového regulárního výrazu vypadá následovně

new RegExp("regulární_výraz", "modifikátory")

přičemž význam proměnných regulární_výraz a modifikátory je stejný jako v předchozím případě. Příklad použití (odpovídá významem předchozímu příkladu):

var myRegVyr=("abc","gi");

Pravidla pro zápis regulárních výrazů jsou poměrně komplikovaná a jejich podrobnější výklad jednak překračuje rozsah tohoto článku a navíc by zde byl značně samoúčelný. Podívejme se tedy na tomto místě pouze na základní konstrukty, které postačí v mnoha běžných situacích.

Za nejjednodušší typ regulárního výrazu lze považovat takový, který se skládá z obvyklých znaků (písmena, čísla, oddělovače apod.). S takovýmto výrazem je shodný řetězec obsahující přesně danou posloupnost znaků (např. abc odpovídá řetězci abc). Použít lze nicméně i tzv. ESC sekvence, s jejichž pomocí se zapisují některé znaky speciální. Konkrétně to mohou být třeba \n (nový řádek) či \t (tabelátor). Dále lze pro zápis znaku použít sekvenci \xnn (znak, který má v ASCII pořadí nn, přičemž nn je šestnáctkové číslo) či \uxxxx (znak, který má v Unicode šestnáctkové pořadí xxxx, např. pevnou mezeru zapíšeme jako \u00A0, znak stupně jako \00B) atp.). Některé obvyklé znaky mohou mít při použití v regulárních výrazech speciální význam (hovoří se pak o nich jako o metaznacích - viz dále), k vypnutí tohoto speciálního významu jim předřadíme lomítko (tedy \\ značí znak lomítka, \* značí hvězdičku, \+ značí plus atp.).

Na síle dodává regulárním výrazům možnost definovat tzv. třídy znaků. Ty určují nikoli jen konkrétní znak, ale celou skupinu znaků splňující zadané kritérium. Konkrétně lze takto použít zápis [znaky], který v hranatých závorkách určuje skupinu znaků do které musí patřit znaky řetězce shodujícího se s daným regulárním výrazem. Skupinu znaků lze přitom zapsat například s pomocí výčtu (např. [abc9] znamená, že se řetězec může skládat pouze se znaků a, b, c a 9) nebo intervalu (zápis [a-z0-9] určuje, že se řetězec může skládat pouze ze znaků v intervalu a-z a číslic od 0 do 9). Zápisem [^znaky] pak naopak určujeme skupinu znaků, které se v řetězci vyskytovat nesmí (takže [^0-9] určuje, že se v řetězci nesmí vyskytovat čísla). Znak tečky . zastupuje libovolný jediný znak, vyjma konce řádku. Sekvence \w stanoví, že daná série znaků patří slovu (ekvivalent zápisu [a-zA-Z0-9_]), \W naopak stanovuje, že znaky nepatří do slova. Zápis \s určuje mezeru či jiný "bílý" znak (tedy třeba i tabulátor, konec řádku atp.), \S naopak libovolný znak, který není mezera v daném smyslu. Číslici určuje zápis \d (ekvivalent [0-9]), znak, který není číslicí pak znak \D.

Znaky opakování, jinak též kvantifikátory, pak určují, kolikrát se shoda může či musí opakovat. Metaznak ? takto určuje jedno nebo žádné opakování, + stanovuje, že se předchozí výraz vyskytuje minimálně jedenkrát, * pak, že předcházející výraz je nalezen v libovolném počtu výskytů, včetně žádného. S pomocí zápisu {n} lze určit, že se předchozí výraz vyskytuje právě n-krát, v případě zápisu {n,} právě n-krát nebo vícekrát a {n,m} pak nejméně n-krát a nejvíce m-krát.

U řetězců lze dále upřesnit jejich pozici, stanovující shodu. Znak ^ takto stanovuje, že se shoda musí vyskytovat na začátku řetězce nebo (byl-li použit u regulárního výrazu modifikátor m pro víceřádkový režim - viz výklad výše) řádku. Obdobně znak $ určuje shodu na konci řetězce či řádku. Výraz \b znamená, že se shoda týká rozhraní mezi slovy, v případě \B pak naopak jakékoli jiné pozice. Výraz (?=vzor) určuje, že následující znaky musí odpovídat výrazu vzor, ale do výsledků se nezahrnují, (?!vzor) naopak říká že následující znaky vzoru nesmí odpovídat.

Konečně je ještě třeba zmínit znaky pro seskupování. Výraz a|b říká, že shoda musí nastat buďto s výrazem a nebo s výrazem b (např. výraz abc|bcd stanovuje shodu s jedním z uvedených výrazů.). Uzavřením výrazu do závorek, tedy použitím vzoru (výraz), dojde k tomu, že je daný výraz zapamatován k pozdějšímu použití jako tzv. podvýraz, a to pod číslem, které odpovídá jeho pořadí ve výrazu. Dále je na něj možno odkazovat při vyhledávání s pomocí zápisu \n, kde n je číslo podvýrazu, či při nahrazování s pomocí zápisu $n, kde n je opět číslo skupiny. Takže například k vyhledání pětiznakového palindromu (řetězce, jež se čte stejně z obou stran) použijeme výraz (.)(.)(.)$2$1.

Tolik tedy v základu o syntaxi regulárních výrazů. Pro podrobnější výklad je zde k dispozici řada zdrojů, jmenujme takto alespoň referenční příručku JavaScriptu a seriál na serveru Interval. Je navíc vhodné zdůraznit, že pro mnoho situací se již někdo pokoušel regulární výraz vytvořit a i když nemusí být syntaxe takovéhoto výrazu stoprocentně spolehlivá a i když je pochopení či úprava regulárního výrazu mnohdy náročnější nežli vytvářet takovýto výraz zcela od základu, jmenujme několik zdrojů takovýchto výrazů. Konkrétně to tedy jsou třeba www.regularnivyrazy.info, Regexp.cz či RegExLib.

Nasazení regulárních výrazů v JavaScriptu

V rámci obecného JavaScriptu lze regulární výrazy použít v podstatě dvěma způsoby. První vychází z odpovídajících metod objektu String, a to především match, search a replace (dále je k dispozici i metoda split, ta pro nás v tuto chvíli nemá přílišný význam).

Match má syntaxi

match(regulární_výraz)

a vyhledává v daném řetězci úseky odpovídající zadanému regulárnímu výrazu, jako výsledek vrací pole s nalezenými výrazy. Pokud není nalezen ani jeden shodný úsek, je výsledkem porovnání hodnota null.

Metoda search má opět syntaxi

search(regulární_výraz)

a vrací index prvního znaku prvního výskytu podřetězce odpovídajícího danému regulárnímu výrazu. Využití dané metody je pro náš účel obvykle malé.

Metoda replace má syntaxi

replace(regulární_výraz,nahrazující_výraz)

Hodnota regulární_výraz zde přitom může být buďto regulárním výrazem nebo obyčejným řetězcem uvedeným v uvozovkách. Nahrazující_výraz je buďto obyčejný řetězec nebo řetězec, který může obsahovat některou z ESC sekvencí používaných v regulárních výrazech, typicky odkaz na skupinu ($1 apod.).

Příklad:

var a="Josef Jan Josef Honza"

b=a.match(/Josef/g);
alert(b);       //vrací pole Josef,Josef

b=a.search(/Jan/);
alert(b);       //vrací hodnotu 6

var a="Josef Novák"
b=a.replace(/(Josef) (Novák)/,"$2, $1");
alert(b);       //vrací řetězec "Novák, Josef"

Druhý, pro naše použití obvykle méně důležitý případ, vychází z použití metod regulárních výrazů samotných. Konkrétně je takto k dispozici metoda test, která má zápis

test(řetězec)

a vrací hodnotu true, pokud řetězec obsahuje daný regulární výraz.

Příklad:

var a=/Honza/;
var string="Josef Jan Josef Honza"


b=a.test("Josef Jan Josef Honza");
alert(b)    //zobrazí "true"

b=a.test("Josef");
alert(b)    //zobrazí "false"

b=a.exec("Josef Jan Josef Honza");
alert(b)    //zobrazí "false"

Dále je zde k dispozici metoda exec, ve tvaru

exec(řetězec)

jež porovná zadaný řetězec s regulárním výrazem a vrátí pole s nalezenými výsledky nebo null, pokud nebylo nic nalezeno. Blíže se jí zde zabývat nebudeme.

Nasazení v InDesignu

V případě nasazení regulárních výrazů přímo při skriptování InDesignu se vyskytuje několik zádrhelů. Především se jedná o to, že jde o prostředek použitelný pro textový obsah, nikoli textové objekty. Změna v dokumentu (story, textovém rámečku atp.) není aplikovatelná přímo (existují možností jak to provést se změnami vlastnosti contents, ale to není vůbec elegantní řešení) a hlavně nedovoluje přímo měnit atributy textu. Obojí (tj. změny i formátování) dokáže měnit funkce hledání/nahrazování InDesignu, ta nicméně regulární výraz přímo jako svůj parametr neakceptuje. Existuje každopádně poměrně jednoduchý postup, jak obejít daný problém, který pro většinu aplikací regulárních výrazů v InDesignu postačí. Schématicky jej lze popsat následovně:

1. Zvolíme určitý textový objekt (story, textový rámeček, dokument, vývěr atp.), jenž chceme prohledávat a dále určíme jeho textový obsah (metoda contents).

2. Provedeme prohledání tohoto textového obsahu metodou match na výskyt daného regulárního výrazu. Získáme tím pole se všemi výskyty.

3. Pro jednotlivé výskyty regulárního výrazu spustíme funkci hledání a záměn InDesignu, kterou aplikujeme na daný textový objekt. To nám dovolí nejen provést náhrady, ale i využít podle libosti formátovací atributy. Při nahrazování přitom můžeme použít řetězce, které vzniknou náhradou v jednotlivých výskytech podle určitých regulárních výrazů.

Celý postup demonstruje následující skript:

//Skript, který v aktuálně vybrané story nalezne všechen text
//ohraničený párovými tagy <b> a </b> (nebo <B> a </B>),
//zformátuje jej znakovým stylem Tučný a tagy odstraní.

//Určíme textový objekt v kterém chceme provést náhradu
var myStory = app.selection[0].parentStory;

//Určíme text z tohoto objektu, který chceme prohledávat regulárním výrazem
var myStoryText=myStory.contents;

//Do pole myFind vložíme všechny řetězce z prohledaného textu, které se shodují se zadaným výrazem
var myFind = myStory.contents.match(/<b>(.*?)<\/b>/gi);


//Bylo-li něco nalezeno, zpracováváme dále
if(myFind != null)
   {
   //Nulujeme preference vyhledávacího dialogu
   app.findPreferences = app.changePreferences = null
   //Cyklus pro všechny nalezené výskyty
   for(i = 0; i<myFind.length; i++){
   //Použijeme funkci pro hledání a záměny InDesignu, provedeme zformátování stylem a náhradou odstraníme tagy
myStory.search(myFind[i],true,true,myFind[i].replace(/<b>(.*?)<\/b>/i,"$1"),{},{appliedCharacterStyle: "Tučný"});
      }
   }

//Pokud se nic nenašlo, rovnou končíme s hlášením
else{alert("Nenalezen žádný řetězec k náhradě")};

Pokud bychom chtěli naznačený princip použít k sérii náhrad tagů a zformátování textu, který byl vytvořen v HTML editoru či exportem z nějaké aplikace (ale předpokládáme že korektně, tj. párové značky pracují dobře), můžeme přepsat algoritmus uvedený výše na funkci a pak jen použít sérii jejich volání s různými hodnotami:

//Funkce pro hledání a náhrady s pomocí regulárních výrazů
//Není univerzální!
//Parametry:
//myChSpace: objekt (prostor) ve kterém budeme hledat a zaměňovat - dokument, story, textový výběr apod.
//myFSpace: text (prostor), který prohledáváme s pomocí regulárního výrazu - obvykle získáme pomocí metody contents
//myFindString: regulární výraz, kterým prohledáváme myFSpace
//myChangeString: regulární výraz, kterým případně můžeme transformovat myFindString
//myFindAt: atributy textu zpřesňující hledání v myChSpace, mají podobu pole dvojic - viz předchozí díly tohoto seriálu
//myChangeAt: atributy textu zpřesňující náhrady v myChSpace, podoba stejná jako u myFindAt
function myRepRegEx(myChSpace,myFSpace,myFindString,myChangeString,myFindAt,myChangeAt)
{var myFind = myStoryText.match(myFindString);
alert(myFind)
if( myFind != null )
   {
   app.findPreferences = app.changePreferences = null
   for( i = 0; i < myFind.length; i++ ){
   //Dopracování vyžaduje nastavení druhého a třetého parametru: zde necháváme rozlišovat malá/velká písmena a hranice slov
   //V nalezených výsledcích transformujeme výsledek záměny podle potřeb nastavením proměnné myChangeString
   myChSpace.search(myFind[i], true, true, myFind[i].replace(myFindString,myChangeString),myFindAt,myChangeAt);
                                        }
   }
else{alert("Nenalezen žádný řetězec k náhradě")};
}

//Volání dané funkce pro jednotlivé tagy
myRepRegEx(myStory,myStoryText,/<b>(.*?)<\/b>/gi,"$1",{},{appliedCharacterStyle: "Tučný"});
myRepRegEx(myStory,myStoryText,/<i>(.*?)<\/i>/gi,"$1",{},{appliedCharacterStyle: "Kurzíva"});
myRepRegEx(myStory,myStoryText,/<code>(.*?)<\/code>/gi,"$1",{},{appliedCharacterStyle: "Kód"});
myRepRegEx(myStory,myStoryText,/<h4>(.*?)<\/h4>/gi,"$1",{},{appliedParagraphStyle: "Nadpis 4"});
//atd.

Aplikovat jej lze i na text připravený původně pro TeX:

//Všechny výskyty řetězce uzavřeného značkami \emph{ a }
//tedy třeba \emph{Josef Novák}
//zformátuje znakovým stylem Index a značky odstraní.
//Obdobně lze zpracovat i rozsáhlé konstrukty z Plain TeXu, LaTeXu apod.
myRepRegEx(myStory,myStoryText,/\\emph\{([^}]+)}/g,"$1",{},{appliedCharacterStyle: "Index"});

Dále se nabízí možnost formátování e-mailové adresy:

//Nejjednodušší forma, dovoluje použít i různé nesprávné adresy, ale validita je zde přece obvykle na tvůrci podkladu
myRepRegEx(myStory,myStoryText,/([a-zA-Z_.]+@[a-zA-Z_.]+)/g,"$1",{},{appliedCharacterStyle: "E-mail"});


//Zde zpracujeme jen e-mailové adresy končící na .cz
myRepRegEx(myStory,myStoryText,/([a-zA-Z_.]+@[a-zA-Z_.]+\.cz)/g,"$1",{},{appliedCharacterStyle: "E-mail"});

či třeba webového odkazu

//Zřejmě nejjednodušší tvar webového odkazu, existuje mnoho přesnějších způsobů zápisu
myRepRegEx(myStory,myStoryText,/(http:\/\/[a-zA-Z_.]+)/gi,"$1",{},{appliedCharacterStyle: "HTTP"});

Pokud máme dokument, ve kterém jsou pěticiferná čísla použita pro PSČ a chceme nesprávný zápis ve tvaru XXXXX nahradit zápisem ve tvaru XXXpevná_mezeraXX, můžeme tak učinit s pomocí následující náhrady:

myRepRegEx(myStory,myStoryText,/\b(\d{3})(\d{2})\b/gi,"$1\u00A0$2",{},{});

Možností, jak aplikovat regulární výrazy, je nepřeberné množství. Zde jsme si ukázali několik málo příkladů náhrad a formátování, ale mnoho aplikací může mít i prosté vyhledávání, jež nám dovolí rychle určit, zda se v dokumentu či jeho části vyskytuje určitý typ řetězce.

Jak si pak lze z našich příkladů i externích zdrojů povšimnout, vytvořit opravdu univerzální, "neprůstřelný" regulární výraz nemusí být vždy úplně snadné. Mnohdy je ale lepší pokusit se napsat něco jednoduššího, co bude fungovat ve většině případů, nežli strávit hledáním vhodného vyjádření více času, než kdybychom mezitím text upravili běžnými prostředky.

Otázkou pak zůstává, jak skriptu předat požadovaný textový rozsah k prohledávání. Mimo triviálních možností (použitých výše) bývá obvyklý požadavek na zjištění veškerého textu v dokumentu. Zde nám může pomoci třeba následující postup:

//Proměnnou myText naplníme obsahem všech story dokumentu
var myDoc=app.activeDocument;
var myText = '';
for(i = 0; myDoc.stories.length > i; i++ ){myText += myDoc.stories[i].contents + ''};

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » VSE  

 » Rubriky  » Go verze  

 » Rubriky  » Sazba  

 » Rubriky  » Polygrafie  

 » Rubriky  » Software  

 

 

 

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: