1146515Sru/* xref.c -- cross references for Texinfo. 2146515Sru $Id: xref.c,v 1.4 2004/12/21 17:28:35 karl Exp $ 3146515Sru 4146515Sru Copyright (C) 2004 Free Software Foundation, Inc. 5146515Sru 6146515Sru This program is free software; you can redistribute it and/or modify 7146515Sru it under the terms of the GNU General Public License as published by 8146515Sru the Free Software Foundation; either version 2, or (at your option) 9146515Sru any later version. 10146515Sru 11146515Sru This program is distributed in the hope that it will be useful, 12146515Sru but WITHOUT ANY WARRANTY; without even the implied warranty of 13146515Sru MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14146515Sru GNU General Public License for more details. 15146515Sru 16146515Sru You should have received a copy of the GNU General Public License 17146515Sru along with this program; if not, write to the Free Software Foundation, 18146515Sru Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 19146515Sru 20146515Sru#include "system.h" 21146515Sru#include "cmds.h" 22146515Sru#include "float.h" 23146515Sru#include "html.h" 24146515Sru#include "index.h" 25146515Sru#include "macro.h" 26146515Sru#include "makeinfo.h" 27146515Sru#include "node.h" 28146515Sru#include "xml.h" 29146515Sru#include "xref.h" 30146515Sru 31146515Sru/* Flags which control initial output string for xrefs. */ 32146515Sruint px_ref_flag = 0; 33146515Sruint ref_flag = 0; 34146515Sru 35146515Sru/* Called in the multiple-argument case to make sure we generate a valid 36146515Sru Info reference. In the single-argument case, the :: we output 37146515Sru suffices for the Info readers to find the end of the reference. */ 38146515Srustatic void 39146515Sruadd_xref_punctuation (void) 40146515Sru{ 41146515Sru if (px_ref_flag || ref_flag) /* user inserts punct after @xref */ 42146515Sru { 43146515Sru /* Check if there's already punctuation. */ 44146515Sru int next_char = next_nonwhitespace_character (); 45146515Sru 46146515Sru if (next_char == -1) 47146515Sru /* EOF while looking for punctuation, let's 48146515Sru insert a period instead of crying. */ 49146515Sru add_char ('.'); 50146515Sru else if (next_char != ',' && next_char != '.') 51146515Sru /* period and comma terminate xrefs, and nothing else. Instead 52146515Sru of generating an Info reference that can't be followed, 53146515Sru though, just insert a period. Not pretty, but functional. */ 54146515Sru add_char ('.'); 55146515Sru } 56146515Sru} 57146515Sru 58146515Sru/* Return next comma-delimited argument, but do not cross a close-brace 59146515Sru boundary. Clean up whitespace, too. If EXPAND is nonzero, replace 60146515Sru the entire brace-delimited argument list with its expansion before 61146515Sru looking for the next comma. */ 62146515Sruchar * 63146515Sruget_xref_token (int expand) 64146515Sru{ 65146515Sru char *string = 0; 66146515Sru 67146515Sru if (docbook) 68146515Sru xml_in_xref_token = 1; 69146515Sru 70146515Sru if (expand) 71146515Sru { 72146515Sru int old_offset = input_text_offset; 73146515Sru int old_lineno = line_number; 74146515Sru 75146515Sru get_until_in_braces ("}", &string); 76146515Sru if (curchar () == '}') /* as opposed to end of text */ 77146515Sru input_text_offset++; 78146515Sru if (input_text_offset > old_offset) 79146515Sru { 80146515Sru int limit = input_text_offset; 81146515Sru 82146515Sru input_text_offset = old_offset; 83146515Sru line_number = old_lineno; 84146515Sru only_macro_expansion++; 85146515Sru replace_with_expansion (input_text_offset, &limit); 86146515Sru only_macro_expansion--; 87146515Sru } 88146515Sru free (string); 89146515Sru } 90146515Sru 91146515Sru get_until_in_braces (",", &string); 92146515Sru if (curchar () == ',') 93146515Sru input_text_offset++; 94146515Sru fix_whitespace (string); 95146515Sru 96146515Sru if (docbook) 97146515Sru xml_in_xref_token = 0; 98146515Sru 99146515Sru return string; 100146515Sru} 101146515Sru 102146515Sru 103146515Sru/* NOTE: If you wonder why the HTML output is produced with such a 104146515Sru peculiar mix of calls to add_word and execute_string, here's the 105146515Sru reason. get_xref_token (1) expands all macros in a reference, but 106146515Sru any other commands, like @value, @@, etc., are left intact. To 107146515Sru expand them, we need to run the arguments through execute_string. 108146515Sru However, characters like <, &, > and others cannot be let into 109146515Sru execute_string, because they will be escaped. See the mess? */ 110146515Sru 111146515Sru/* Make a cross reference. */ 112146515Sruvoid 113146515Srucm_xref (int arg) 114146515Sru{ 115146515Sru if (arg == START) 116146515Sru { 117146515Sru char *arg1 = get_xref_token (1); /* expands all macros in xref */ 118146515Sru char *arg2 = get_xref_token (0); 119146515Sru char *arg3 = get_xref_token (0); 120146515Sru char *arg4 = get_xref_token (0); 121146515Sru char *arg5 = get_xref_token (0); 122146515Sru char *tem; 123146515Sru 124146515Sru /* "@xref{,Foo,, Bar, Baz} is not valid usage of @xref. The 125146515Sru first argument must never be blank." --rms. 126146515Sru We hereby comply by disallowing such constructs. */ 127146515Sru if (!*arg1) 128146515Sru line_error (_("First argument to cross-reference may not be empty")); 129146515Sru 130146515Sru if (docbook) 131146515Sru { 132146515Sru if (!ref_flag) 133146515Sru add_word (px_ref_flag || printing_index 134146515Sru ? (char *) _("see ") : (char *) _("See ")); 135146515Sru 136146515Sru if (!*arg4 && !*arg5) 137146515Sru { 138146515Sru char *arg1_id = xml_id (arg1); 139146515Sru 140146515Sru if (*arg2 || *arg3) 141146515Sru { 142146515Sru xml_insert_element_with_attribute (XREFNODENAME, START, 143146515Sru "linkend=\"%s\"", arg1_id); 144146515Sru free (arg1_id); 145146515Sru execute_string ("%s", *arg3 ? arg3 : arg2); 146146515Sru xml_insert_element (XREFNODENAME, END); 147146515Sru } 148146515Sru else 149146515Sru { 150146515Sru xml_insert_element_with_attribute (XREF, START, 151146515Sru "linkend=\"%s\"", arg1_id); 152146515Sru xml_insert_element (XREF, END); 153146515Sru free (arg1_id); 154146515Sru } 155146515Sru } 156146515Sru else if (*arg5) 157146515Sru { 158146515Sru add_word_args (_("See section ``%s'' in "), *arg3 ? arg3 : arg1); 159146515Sru xml_insert_element (CITE, START); 160146515Sru add_word (arg5); 161146515Sru xml_insert_element (CITE, END); 162146515Sru } 163146515Sru else if (*arg4) 164146515Sru { 165146515Sru /* Very sad, we are losing xrefs made to ``info only'' books. */ 166146515Sru } 167146515Sru } 168146515Sru else if (xml) 169146515Sru { 170146515Sru if (!ref_flag) 171146515Sru add_word_args ("%s", px_ref_flag ? _("see ") : _("See ")); 172146515Sru 173146515Sru xml_insert_element (XREF, START); 174146515Sru xml_insert_element (XREFNODENAME, START); 175146515Sru execute_string ("%s", arg1); 176146515Sru xml_insert_element (XREFNODENAME, END); 177146515Sru if (*arg2) 178146515Sru { 179146515Sru xml_insert_element (XREFINFONAME, START); 180146515Sru execute_string ("%s", arg2); 181146515Sru xml_insert_element (XREFINFONAME, END); 182146515Sru } 183146515Sru if (*arg3) 184146515Sru { 185146515Sru xml_insert_element (XREFPRINTEDDESC, START); 186146515Sru execute_string ("%s", arg3); 187146515Sru xml_insert_element (XREFPRINTEDDESC, END); 188146515Sru } 189146515Sru if (*arg4) 190146515Sru { 191146515Sru xml_insert_element (XREFINFOFILE, START); 192146515Sru execute_string ("%s", arg4); 193146515Sru xml_insert_element (XREFINFOFILE, END); 194146515Sru } 195146515Sru if (*arg5) 196146515Sru { 197146515Sru xml_insert_element (XREFPRINTEDNAME, START); 198146515Sru execute_string ("%s", arg5); 199146515Sru xml_insert_element (XREFPRINTEDNAME, END); 200146515Sru } 201146515Sru xml_insert_element (XREF, END); 202146515Sru } 203146515Sru else if (html) 204146515Sru { 205146515Sru if (!ref_flag) 206146515Sru add_word_args ("%s", px_ref_flag ? _("see ") : _("See ")); 207146515Sru } 208146515Sru else 209146515Sru add_word_args ("%s", px_ref_flag ? "*note " : "*Note "); 210146515Sru 211146515Sru if (!xml) 212146515Sru { 213146515Sru if (*arg5 || *arg4) 214146515Sru { 215146515Sru /* arg1 - node name 216146515Sru arg2 - reference name 217146515Sru arg3 - title or topic (and reference name if arg2 is NULL) 218146515Sru arg4 - info file name 219146515Sru arg5 - printed manual title */ 220146515Sru char *ref_name; 221146515Sru 222146515Sru if (!*arg2) 223146515Sru { 224146515Sru if (*arg3) 225146515Sru ref_name = arg3; 226146515Sru else 227146515Sru ref_name = arg1; 228146515Sru } 229146515Sru else 230146515Sru ref_name = arg2; 231146515Sru 232146515Sru if (html) 233146515Sru { /* More to do eventually, down to Unicode 234146515Sru Normalization Form C. See the HTML Xref nodes in 235146515Sru the manual. */ 236146515Sru char *file_arg = arg4; 237146515Sru add_html_elt ("<a href="); 238146515Sru 239146515Sru { 240146515Sru /* If there's a directory part, ignore it. */ 241146515Sru char *p = strrchr (file_arg, '/'); 242146515Sru if (p) 243146515Sru file_arg = p + 1; 244146515Sru 245146515Sru /* If there's a dot, make it a NULL terminator, so the 246146515Sru extension does not get into the way. */ 247146515Sru p = strrchr (file_arg , '.'); 248146515Sru if (p != NULL) 249146515Sru *p = 0; 250146515Sru } 251146515Sru 252146515Sru if (! *file_arg) 253146515Sru warning (_("Empty file name for HTML cross reference in `%s'"), 254146515Sru arg4); 255146515Sru 256146515Sru /* Note that if we are splitting, and the referenced 257146515Sru tag is an anchor rather than a node, we will 258146515Sru produce a reference to a file whose name is 259146515Sru derived from the anchor name. However, only 260146515Sru nodes create files, so we are referencing a 261146515Sru non-existent file. cm_anchor, which see, deals 262146515Sru with that problem. */ 263146515Sru if (splitting) 264146515Sru execute_string ("\"../%s/", file_arg); 265146515Sru else 266146515Sru execute_string ("\"%s.html", file_arg); 267146515Sru /* Do not collapse -- to -, etc., in references. */ 268146515Sru in_fixed_width_font++; 269146515Sru tem = expansion (arg1, 0); /* expand @-commands in node */ 270146515Sru in_fixed_width_font--; 271146515Sru add_anchor_name (tem, 1); 272146515Sru free (tem); 273146515Sru add_word ("\">"); 274146515Sru execute_string ("%s",ref_name); 275146515Sru add_word ("</a>"); 276146515Sru } 277146515Sru else 278146515Sru { 279146515Sru execute_string ("%s:", ref_name); 280146515Sru in_fixed_width_font++; 281146515Sru execute_string (" (%s)%s", arg4, arg1); 282146515Sru add_xref_punctuation (); 283146515Sru in_fixed_width_font--; 284146515Sru } 285146515Sru 286146515Sru /* Free all of the arguments found. */ 287146515Sru if (arg1) free (arg1); 288146515Sru if (arg2) free (arg2); 289146515Sru if (arg3) free (arg3); 290146515Sru if (arg4) free (arg4); 291146515Sru if (arg5) free (arg5); 292146515Sru return; 293146515Sru } 294146515Sru else 295146515Sru remember_node_reference (arg1, line_number, followed_reference); 296146515Sru 297146515Sru if (*arg3) 298146515Sru { 299146515Sru if (html) 300146515Sru { 301146515Sru add_html_elt ("<a href=\""); 302146515Sru in_fixed_width_font++; 303146515Sru tem = expansion (arg1, 0); 304146515Sru in_fixed_width_font--; 305146515Sru add_anchor_name (tem, 1); 306146515Sru free (tem); 307146515Sru add_word ("\">"); 308146515Sru execute_string ("%s", *arg2 ? arg2 : arg3); 309146515Sru add_word ("</a>"); 310146515Sru } 311146515Sru else 312146515Sru { 313146515Sru execute_string ("%s:", *arg2 ? arg2 : arg3); 314146515Sru in_fixed_width_font++; 315146515Sru execute_string (" %s", arg1); 316146515Sru add_xref_punctuation (); 317146515Sru in_fixed_width_font--; 318146515Sru } 319146515Sru } 320146515Sru else 321146515Sru { 322146515Sru if (html) 323146515Sru { 324146515Sru add_html_elt ("<a href=\""); 325146515Sru in_fixed_width_font++; 326146515Sru tem = expansion (arg1, 0); 327146515Sru in_fixed_width_font--; 328146515Sru add_anchor_name (tem, 1); 329146515Sru free (tem); 330146515Sru add_word ("\">"); 331146515Sru if (*arg2) 332146515Sru execute_string ("%s", arg2); 333146515Sru else 334146515Sru { 335146515Sru char *fref = get_float_ref (arg1); 336146515Sru execute_string ("%s", fref ? fref : arg1); 337146515Sru free (fref); 338146515Sru } 339146515Sru add_word ("</a>"); 340146515Sru } 341146515Sru else 342146515Sru { 343146515Sru if (*arg2) 344146515Sru { 345146515Sru execute_string ("%s:", arg2); 346146515Sru in_fixed_width_font++; 347146515Sru execute_string (" %s", arg1); 348146515Sru add_xref_punctuation (); 349146515Sru in_fixed_width_font--; 350146515Sru } 351146515Sru else 352146515Sru { 353146515Sru char *fref = get_float_ref (arg1); 354146515Sru if (fref) 355146515Sru { /* Reference is being made to a float. */ 356146515Sru execute_string ("%s:", fref); 357146515Sru in_fixed_width_font++; 358146515Sru execute_string (" %s", arg1); 359146515Sru add_xref_punctuation (); 360146515Sru in_fixed_width_font--; 361146515Sru } 362146515Sru else 363146515Sru { 364146515Sru in_fixed_width_font++; 365146515Sru execute_string ("%s::", arg1); 366146515Sru in_fixed_width_font--; 367146515Sru } 368146515Sru } 369146515Sru } 370146515Sru } 371146515Sru } 372146515Sru /* Free all of the arguments found. */ 373146515Sru if (arg1) free (arg1); 374146515Sru if (arg2) free (arg2); 375146515Sru if (arg3) free (arg3); 376146515Sru if (arg4) free (arg4); 377146515Sru if (arg5) free (arg5); 378146515Sru } 379146515Sru else 380146515Sru { /* Check that the next non-whitespace character is valid to follow 381146515Sru an xref (so Info readers can find the node names). 382146515Sru `input_text_offset' is pointing at the "}" which ended the xref 383146515Sru command. This is not used for @pxref or @ref, since we insert 384146515Sru the necessary punctuation above, if needed. */ 385146515Sru int temp = next_nonwhitespace_character (); 386146515Sru 387146515Sru if (temp == -1) 388146515Sru warning (_("End of file reached while looking for `.' or `,'")); 389146515Sru else if (temp != '.' && temp != ',') 390146515Sru warning (_("`.' or `,' must follow @%s, not `%c'"), command, temp); 391146515Sru } 392146515Sru} 393146515Sru 394146515Sruvoid 395146515Srucm_pxref (int arg) 396146515Sru{ 397146515Sru if (arg == START) 398146515Sru { 399146515Sru px_ref_flag++; 400146515Sru cm_xref (arg); 401146515Sru px_ref_flag--; 402146515Sru } 403146515Sru /* cm_xref isn't called with arg == END, which disables the code near 404146515Sru the end of cm_xref that checks for `.' or `,' after the 405146515Sru cross-reference. This is because cm_xref generates the required 406146515Sru character itself (when needed) if px_ref_flag is set. */ 407146515Sru} 408146515Sru 409146515Sruvoid 410146515Srucm_ref (int arg) 411146515Sru{ 412146515Sru /* See the comments in cm_pxref about the checks for punctuation. */ 413146515Sru if (arg == START) 414146515Sru { 415146515Sru ref_flag++; 416146515Sru cm_xref (arg); 417146515Sru ref_flag--; 418146515Sru } 419146515Sru} 420146515Sru 421146515Sruvoid 422146515Srucm_inforef (int arg) 423146515Sru{ 424146515Sru if (arg == START) 425146515Sru { 426146515Sru char *node = get_xref_token (1); /* expands all macros in inforef */ 427146515Sru char *pname = get_xref_token (0); 428146515Sru char *file = get_xref_token (0); 429146515Sru 430146515Sru /* (see comments at cm_xref). */ 431146515Sru if (!*node) 432146515Sru line_error (_("First argument to @inforef may not be empty")); 433146515Sru 434146515Sru if (xml && !docbook) 435146515Sru { 436146515Sru xml_insert_element (INFOREF, START); 437146515Sru xml_insert_element (INFOREFNODENAME, START); 438146515Sru execute_string ("%s", node); 439146515Sru xml_insert_element (INFOREFNODENAME, END); 440146515Sru if (*pname) 441146515Sru { 442146515Sru xml_insert_element (INFOREFREFNAME, START); 443146515Sru execute_string ("%s", pname); 444146515Sru xml_insert_element (INFOREFREFNAME, END); 445146515Sru } 446146515Sru xml_insert_element (INFOREFINFONAME, START); 447146515Sru execute_string ("%s", file); 448146515Sru xml_insert_element (INFOREFINFONAME, END); 449146515Sru 450146515Sru xml_insert_element (INFOREF, END); 451146515Sru } 452146515Sru else if (html) 453146515Sru { 454146515Sru char *tem; 455146515Sru 456146515Sru add_word ((char *) _("see ")); 457146515Sru /* html fixxme: revisit this */ 458146515Sru add_html_elt ("<a href="); 459146515Sru if (splitting) 460146515Sru execute_string ("\"../%s/", file); 461146515Sru else 462146515Sru execute_string ("\"%s.html", file); 463146515Sru tem = expansion (node, 0); 464146515Sru add_anchor_name (tem, 1); 465146515Sru add_word ("\">"); 466146515Sru execute_string ("%s", *pname ? pname : tem); 467146515Sru add_word ("</a>"); 468146515Sru free (tem); 469146515Sru } 470146515Sru else 471146515Sru { 472146515Sru if (*pname) 473146515Sru execute_string ("*note %s: (%s)%s", pname, file, node); 474146515Sru else 475146515Sru execute_string ("*note (%s)%s::", file, node); 476146515Sru } 477146515Sru 478146515Sru free (node); 479146515Sru free (pname); 480146515Sru free (file); 481146515Sru } 482146515Sru} 483146515Sru 484146515Sru/* A URL reference. */ 485146515Sruvoid 486146515Srucm_uref (int arg) 487146515Sru{ 488146515Sru if (arg == START) 489146515Sru { 490146515Sru extern int printing_index; 491146515Sru char *url = get_xref_token (1); /* expands all macros in uref */ 492146515Sru char *desc = get_xref_token (0); 493146515Sru char *replacement = get_xref_token (0); 494146515Sru 495146515Sru if (docbook) 496146515Sru { 497146515Sru xml_insert_element_with_attribute (UREF, START, "url=\"%s\"", 498146515Sru text_expansion (url)); 499146515Sru if (*replacement) 500146515Sru execute_string ("%s", replacement); 501146515Sru else if (*desc) 502146515Sru execute_string ("%s", desc); 503146515Sru else 504146515Sru execute_string ("%s", url); 505146515Sru xml_insert_element (UREF, END); 506146515Sru } 507146515Sru else if (xml) 508146515Sru { 509146515Sru xml_insert_element (UREF, START); 510146515Sru xml_insert_element (UREFURL, START); 511146515Sru execute_string ("%s", url); 512146515Sru xml_insert_element (UREFURL, END); 513146515Sru if (*desc) 514146515Sru { 515146515Sru xml_insert_element (UREFDESC, START); 516146515Sru execute_string ("%s", desc); 517146515Sru xml_insert_element (UREFDESC, END); 518146515Sru } 519146515Sru if (*replacement) 520146515Sru { 521146515Sru xml_insert_element (UREFREPLACEMENT, START); 522146515Sru execute_string ("%s", replacement); 523146515Sru xml_insert_element (UREFREPLACEMENT, END); 524146515Sru } 525146515Sru xml_insert_element (UREF, END); 526146515Sru } 527146515Sru else if (html) 528146515Sru { /* never need to show the url */ 529146515Sru add_html_elt ("<a href="); 530146515Sru /* don't collapse `--' etc. in the url */ 531146515Sru in_fixed_width_font++; 532146515Sru execute_string ("\"%s\"", url); 533146515Sru in_fixed_width_font--; 534146515Sru add_word (">"); 535146515Sru execute_string ("%s", *replacement ? replacement 536146515Sru : (*desc ? desc : url)); 537146515Sru add_word ("</a>"); 538146515Sru } 539146515Sru else if (*replacement) /* do not show the url */ 540146515Sru execute_string ("%s", replacement); 541146515Sru else if (*desc) /* show both text and url */ 542146515Sru { 543146515Sru execute_string ("%s ", desc); 544146515Sru in_fixed_width_font++; 545146515Sru execute_string ("(%s)", url); 546146515Sru in_fixed_width_font--; 547146515Sru } 548146515Sru else /* no text at all, so have the url to show */ 549146515Sru { 550146515Sru in_fixed_width_font++; 551146515Sru execute_string ("%s%s%s", 552146515Sru printing_index ? "" : "`", 553146515Sru url, 554146515Sru printing_index ? "" : "'"); 555146515Sru in_fixed_width_font--; 556146515Sru } 557146515Sru if (url) 558146515Sru free (url); 559146515Sru if (desc) 560146515Sru free (desc); 561146515Sru if (replacement) 562146515Sru free (replacement); 563146515Sru } 564146515Sru} 565146515Sru 566146515Sru/* An email reference. */ 567146515Sruvoid 568146515Srucm_email (int arg) 569146515Sru{ 570146515Sru if (arg == START) 571146515Sru { 572146515Sru char *addr = get_xref_token (1); /* expands all macros in email */ 573146515Sru char *name = get_xref_token (0); 574146515Sru 575146515Sru if (xml && docbook) 576146515Sru { 577146515Sru xml_insert_element_with_attribute (EMAIL, START, "url=\"mailto:%s\"", addr); 578146515Sru if (*name) 579146515Sru execute_string ("%s", name); 580146515Sru xml_insert_element (EMAIL, END); 581146515Sru } 582146515Sru else if (xml) 583146515Sru { 584146515Sru xml_insert_element (EMAIL, START); 585146515Sru xml_insert_element (EMAILADDRESS, START); 586146515Sru execute_string ("%s", addr); 587146515Sru xml_insert_element (EMAILADDRESS, END); 588146515Sru if (*name) 589146515Sru { 590146515Sru xml_insert_element (EMAILNAME, START); 591146515Sru execute_string ("%s", name); 592146515Sru xml_insert_element (EMAILNAME, END); 593146515Sru } 594146515Sru xml_insert_element (EMAIL, END); 595146515Sru } 596146515Sru else if (html) 597146515Sru { 598146515Sru add_html_elt ("<a href="); 599146515Sru /* don't collapse `--' etc. in the address */ 600146515Sru in_fixed_width_font++; 601146515Sru execute_string ("\"mailto:%s\"", addr); 602146515Sru in_fixed_width_font--; 603146515Sru add_word (">"); 604146515Sru execute_string ("%s", *name ? name : addr); 605146515Sru add_word ("</a>"); 606146515Sru } 607146515Sru else 608146515Sru { 609146515Sru execute_string ("%s%s", name, *name ? " " : ""); 610146515Sru in_fixed_width_font++; 611146515Sru execute_string ("<%s>", addr); 612146515Sru in_fixed_width_font--; 613146515Sru } 614146515Sru 615146515Sru if (addr) 616146515Sru free (addr); 617146515Sru if (name) 618146515Sru free (name); 619146515Sru } 620146515Sru} 621