convert_wiki.pl (20532B)
1 #!/usr/bin/perl 2 # 3 # convert_wiki.pl - Transform an old-style wiki into the new format 4 # 5 # This script assumes that a wiki has testable statement entries 6 # with varying lemgth lines. Those lines will be converted into 7 # the format described by the specification at 8 # https://spec-ops.github.io/atta-api/index.html 9 # 10 # usage: convert_wiki.pl -f file | -w wiki_title -o outFile 11 12 use strict; 13 14 use IO::String ; 15 use JSON ; 16 use MediaWiki::API ; 17 use Getopt::Long; 18 19 my @apiNames = qw(UIA MSAA ATK IAccessible2 AXAPI); 20 21 # dir is determined based upon the short name of the spec and is defined 22 # by the input or on the command line 23 24 my $file = undef ; 25 my $spec = undef ; 26 my $wiki_title = undef ; 27 my $dir = undef; 28 my $outFile = undef; 29 30 my $result = GetOptions( 31 "f|file=s" => \$file, 32 "w|wiki=s" => \$wiki_title, 33 "s|spec=s" => \$spec, 34 "o|output=s" => \$outFile); 35 36 my $wiki_config = { 37 "api_url" => "https://www.w3.org/wiki/api.php" 38 }; 39 40 my %specs = ( 41 "aria11" => { 42 title => "ARIA_1.1_Testable_Statements", 43 specURL => "https://www.w3.org/TR/wai-aria11" 44 }, 45 "svg" => { 46 title => "SVG_Accessibility/Testing/Test_Assertions_with_Tables_for_ATTA", 47 specURL => "https://www.w3.org/TR/svg-aam-1.0/" 48 } 49 ); 50 51 my $io ; 52 our $theSpecURL = ""; 53 54 if ($spec) { 55 $wiki_title = $specs{$spec}->{title}; 56 $theSpecURL = $specs{$spec}->{specURL}; 57 } 58 59 if ($wiki_title) { 60 my $MW = MediaWiki::API->new( $wiki_config ); 61 my $page = $MW->get_page( { title => $wiki_title } ); 62 my $theContent = $page->{'*'}; 63 $io = IO::String->new($theContent); 64 } elsif ($file) { 65 open($io, "<", $file) || die("Failed to open $file: " . $@); 66 } else { 67 usage() ; 68 } 69 70 my $outH ; 71 if (defined $outFile) { 72 open($outH, ">", $outFile) || die("Failed to create file $outFile: $@"); 73 } else { 74 $outH = new IO::Handle; 75 $outH->fdopen(fileno(STDOUT), "w"); 76 } 77 78 79 # Now let's walk through the content and spit it back out 80 # transformed 81 # 82 83 # iterate over the content 84 85 # my $io ; 86 # open($io, "<", "raw") ; 87 88 my $state = 0; # between items 89 my $theCode = ""; 90 my $theAttributes = {}; 91 my $theAsserts = {} ; 92 my $theAssertCount = 0; 93 my $theAPI = ""; 94 my $typeRows = 0; 95 my $theType = ""; 96 my $theName = ""; 97 my $theRef = ""; 98 99 my $before = "" ; 100 my $after = "" ; 101 102 my @errors = () ; 103 my $linecount = 0; 104 105 while (<$io>) { 106 $linecount++; 107 # look for state 108 if ($state == 0) { 109 if (scalar(keys(%$theAsserts))) { 110 # we were in an item; dump it 111 print $outH dump_table($theAsserts) ; 112 $theAsserts = {}; 113 } 114 print $outH $_; 115 } 116 if (m/^\{\|/) { 117 # table started 118 $state = 4; 119 $theAPI = ""; 120 } 121 if ($state == 4) { 122 if (m/^\|-/) { 123 if ($theAPI 124 && exists($theAsserts->{$theAPI}->[$theAssertCount]) 125 && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { 126 $theAssertCount++; 127 } 128 # start of a table row 129 if ($theType ne "" && $typeRows) { 130 # we are still processing items for a type 131 $typeRows--; 132 # populate the first cell 133 $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; 134 } else { 135 $theType = ""; 136 } 137 } elsif (m/^\|\}/) { 138 # ran out of table 139 $state = 0; 140 } elsif (m/^\|rowspan="*([0-9])"*\|(.*)$/) { 141 # someone put a rowspan in here.. is ht an API? 142 my $rows = $1; 143 my $theString = $2; 144 $theString =~ s/ +$//; 145 $theString =~ s/^ +//; 146 $theString = "IAccessible2" if ($theString eq "IA2") ; 147 if (grep { $_ eq $theString } @apiNames) { 148 $theAssertCount = 0; 149 # this is a new API section 150 $theAPI = $theString ; 151 $theAsserts->{$theAPI} = [ [] ] ; 152 $theType = ""; 153 } else { 154 # nope, this is a multi-row type 155 $theType = $theString; 156 $typeRows = $rows; 157 $typeRows--; 158 # populate the first cell 159 if ($theAPI 160 && exists($theAsserts->{$theAPI}->[$theAssertCount]) 161 && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { 162 $theAssertCount++; 163 } 164 $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; 165 } 166 } elsif (m/^\|note/) { 167 # there is a note in the table... throw it out 168 # and the next line too 169 my $l = <$io>; 170 } elsif (m/^\|(MSAA|UIA|IA2|IAccessible2|ATK|AXAPI) *$/) { 171 # they FORGOT a rowspan on an API. That means there is only 1 172 # row 173 my $theString = $1; 174 $theString =~ s/ +$//; 175 $theString =~ s/^ +//; 176 $theString = "IAccessible2" if ($theString eq "IA2") ; 177 if (grep { $_ eq $theString } @apiNames) { 178 $theAssertCount = 0; 179 # this is a new API section 180 $theAPI = $theString ; 181 $theAsserts->{$theAPI} = [ [] ] ; 182 $theType = ""; 183 } else { 184 push(@errors, "Bad API Name at $linecount: $theString"); 185 } 186 } elsif (m/^\|(.*)$/) { 187 my $item = $1; 188 $item =~ s/^ *//; 189 $item =~ s/ *$//; 190 $item =~ s/^['"]//; 191 $item =~ s/['"]$//; 192 # add into the data structure for the API 193 if (!exists $theAsserts->{$theAPI}->[$theAssertCount]) { 194 $theAsserts->{$theAPI}->[$theAssertCount] = [ $item ] ; 195 } else { 196 push(@{$theAsserts->{$theAPI}->[$theAssertCount]}, $item); 197 } 198 } 199 next; 200 } 201 }; 202 203 if ($state == 0) { 204 if (scalar(keys(%$theAsserts))) { 205 # we were in an item; dump it 206 print $outH dump_table($theAsserts) ; 207 } 208 } 209 210 if (@errors) { 211 print "There were the following errors:\n"; 212 foreach my $err (@errors) { 213 print $err . "\n"; 214 } 215 } 216 217 exit 0; 218 219 220 sub dump_table() { 221 my $asserts = shift; 222 223 if (!scalar(keys(%$asserts)) ) { 224 # no actual assertions 225 return ""; 226 } 227 228 my $output = "" ; 229 230 my @keywords = qw(property result event); 231 232 foreach my $API (sort(keys(%$asserts))) { 233 # looking at each API in turn 234 my $ref = $asserts->{$API}; 235 my $rowcount = scalar(@$ref) ; 236 # $output .= "|-\n|rowspan=$count|$API\n" ; 237 # now we are in the assertions; special case each API 238 my @conditions = @$ref; 239 for (my $i = 0; $i < scalar(@conditions); $i++) { 240 my (@new, @additional) ; 241 if ($i) { 242 $output .= "|-\n"; 243 } 244 if ($API eq "ATK") { 245 my $start = 0; 246 my $assert = "is"; 247 if ($conditions[$i]->[0] =~ m/^NOT/) { 248 $start = 1; 249 $assert = "isNot"; 250 } 251 252 if ($conditions[$i]->[$start] =~ m/^ROLE_/) { 253 $new[0] = "property"; 254 $new[1] = "role"; 255 $new[2] = $assert; 256 $new[3] = $conditions[$i]->[$start]; 257 } elsif ($conditions[$i]->[$start] =~ m/^xml-roles/) { 258 $new[0] = "property"; 259 $new[1] = "role"; 260 $new[2] = $assert; 261 $new[3] = $conditions[$i]->[$start+1]; 262 } elsif ($conditions[$i]->[$start] =~ m/^description/) { 263 my $id = $conditions[$i]->[$start+1]; 264 $new[0] = "property"; 265 $new[1] = "description"; 266 $new[2] = $assert; 267 $new[3] = $id; 268 push(@{$additional[0]}, ("relation", "RELATION_DESCRIBED_BY", $assert, $id)); 269 push(@{$additional[1]}, ("relation", "RELATION_DESCRIPTION_FOR", $assert, "test")); 270 } elsif ($conditions[$i]->[$start] =~ m/not in accessibility tree/i) { 271 @new = qw(property accessible exists false); 272 } elsif ($conditions[$i]->[$start] =~ m/^RELATION/) { 273 $new[0] = "relation"; 274 $new[1] = $conditions[$i]->[$start]; 275 $new[2] = $assert; 276 $new[3] = $conditions[$i]->[$start+1]; 277 } elsif ($conditions[$i]->[$start] =~ m/(.*) interface/i) { 278 $new[0] = "property"; 279 $new[1] = "interfaces"; 280 $new[3] = $1; 281 if ($conditions[$i]->[$start+1] ne '<shown>' 282 && $conditions[$i]->[$start+1] !~ m/true/i ) { 283 $assert = "doesNotContain"; 284 } else { 285 $assert = "contains"; 286 } 287 $new[2] = $assert; 288 } elsif ($conditions[$i]->[$start] eq "object" || $conditions[$i]->[$start] eq "attribute" ) { 289 $new[0] = "property"; 290 $new[1] = "objectAttributes"; 291 my $val = $conditions[$i]->[2]; 292 $val =~ s/"//g; 293 $new[3] = $conditions[$i]->[1] . ":" . $val; 294 if ($conditions[$i]->[1] eq "not exposed" 295 || $conditions[$i]->[2] eq "false") { 296 $new[2] = "doesNotContain"; 297 } else { 298 $new[2] = "contains"; 299 } 300 } elsif ($conditions[$i]->[$start] =~ m/^STATE_/) { 301 $new[0] = "property"; 302 $new[1] = "states"; 303 $new[3] = $conditions[$i]->[$start]; 304 if ($assert eq "true") { 305 $new[2] = "contains"; 306 } else { 307 $new[2] = "doesNotContain"; 308 } 309 } elsif ($conditions[$i]->[$start] =~ m/^object attribute (.*)/) { 310 my $name = $1; 311 $new[0] = "property"; 312 $new[0] = "objectAttributes"; 313 my $val = $conditions[$i]->[1]; 314 $val =~ s/"//g; 315 if ($val eq "not exposed" || $val eq "not mapped") { 316 $new[3] = $name; 317 $new[2] = "doesNotContain"; 318 } else { 319 $new[3] = $name . ":" . $val; 320 $new[2] = "contains"; 321 } 322 } elsif ($conditions[$i]->[$start] =~ m/^name/) { 323 my $name = $conditions[$i]->[1]; 324 my $cond = "is" ; 325 if ($name eq "<empty>" ) { 326 $cond = "empty"; 327 $name = "true" 328 } elsif ($name eq "<not empty>") { 329 $cond = "empty"; 330 $name = "false"; 331 } 332 $new[0] = "property"; 333 $new[1] = "name"; 334 $new[2] = $cond; 335 $new[3] = $name; 336 } else { 337 @new = @{$conditions[$i]}; 338 if ($conditions[$i]->[2] eq '<shown>') { 339 $new[2] = "contains"; 340 } 341 } 342 $conditions[$i] = \@new; 343 } elsif ($API eq "UIA") { 344 my $start = 0; 345 my $assert = "is"; 346 if ($conditions[$i]->[$start] =~ m/\./) { 347 my $val = $conditions[$i]->[$start+1]; 348 $val =~ s/"//g; 349 $val =~ s/'//g; 350 $new[0] = "result"; 351 $new[1] = $conditions[$i]->[$start]; 352 $new[2] = $assert; 353 $new[3] = $conditions[$i]->[$start+1]; 354 } elsif ($conditions[$i]->[$start] =~ m/not in accessibility tree/i) { 355 @new = qw(property accessible exists false); 356 } elsif ($conditions[$i]->[$start] =~ m/^(AriaProperties|Toggle|ExpandCollapse)/) { 357 my $name = $conditions[$i]->[1]; 358 $new[0] = "property"; 359 $new[1] = $1; 360 my $val = $conditions[$i]->[2]; 361 $val =~ s/"//g; 362 if ($val eq "not exposed" || $val eq "not mapped") { 363 $new[3] = $name; 364 $new[2] = "doesNotContain"; 365 } else { 366 $new[3] = $name . ":" . $val; 367 $new[2] = "contains"; 368 } 369 } elsif ($conditions[$i]->[$start] =~ m/^LabeledBy/i) { 370 $new[0] = "property"; 371 $new[1] = $conditions[$i]->[$start]; 372 $new[2] = $assert; 373 $new[3] = $conditions[$i]->[$start+1]; 374 } elsif ($conditions[$i]->[$start] =~ m/^Name/) { 375 my $name = $conditions[$i]->[1]; 376 my $cond = "is" ; 377 if ($name eq "<empty>" ) { 378 $cond = "empty"; 379 $name = "true" 380 } elsif ($name eq "<not empty>") { 381 $cond = "empty"; 382 $name = "false"; 383 } 384 $new[0] = "property"; 385 $new[1] = "Name"; 386 $new[2] = $cond; 387 $new[3] = $name; 388 } elsif ($conditions[$i]->[$start] =~ m/^TBD/) { 389 $new[0] = "TBD"; 390 $new[1] = $new[2] = $new[3] = ""; 391 } else { 392 if ($conditions[$i]->[1] ne '<shown>' 393 && $conditions[$i]->[1] !~ m/true/i ) { 394 $assert = "isNot"; 395 } else { 396 $assert = "is"; 397 } 398 $new[0] = "property"; 399 $new[1] = $conditions[$i]->[$start]; 400 $new[2] = $assert; 401 $new[3] = $conditions[$i]->[$start+1]; 402 } 403 } elsif ($API eq "MSAA") { 404 my $start = 0; 405 my $assert = "is"; 406 if ($conditions[$i]->[0] =~ m/^NOT/) { 407 $start = 1; 408 $assert = "isNot"; 409 } 410 411 if ($conditions[$i]->[$start] =~ m/^role/) { 412 $new[0] = "property"; 413 $new[1] = "role"; 414 $new[2] = $assert; 415 $new[3] = $conditions[$i]->[$start+1]; 416 } elsif ($conditions[$i]->[$start] =~ m/^xml-roles/) { 417 $new[0] = "property"; 418 $new[1] = "role"; 419 $new[2] = $assert; 420 $new[3] = $conditions[$i]->[$start+1]; 421 } elsif ($conditions[$i]->[$start] =~ m/not in accessibility tree/i) { 422 @new = qw(property accessible exists false); 423 } elsif ($conditions[$i]->[$start] =~ m/^(accName|accDescription)/) { 424 my $name = $conditions[$i]->[$start+1]; 425 my $cond = "is" ; 426 if ($name eq "<empty>" ) { 427 $cond = "empty"; 428 $name = "true" 429 } elsif ($name eq "<not empty>") { 430 $cond = "empty"; 431 $name = "false"; 432 } 433 $new[0] = "property"; 434 $new[1] = $conditions[$i]->[$start]; 435 $new[2] = $cond; 436 $new[3] = $name; 437 } elsif ($conditions[$i]->[$start] =~ m/^ROLE_/) { 438 $new[0] = "property"; 439 $new[1] = "role"; 440 $new[2] = $assert; 441 $new[3] = $conditions[$i]->[$start]; 442 } elsif ($conditions[$i]->[$start] =~ m/^(STATE_.*) *([^ ]*)/) { 443 $new[0] = "property"; 444 $new[1] = "states"; 445 $new[3] = $1; 446 if ($2 && $2 eq "cleared") { 447 print "MATCHED $1, $2\n"; 448 $new[2] = "doesNotContain"; 449 } else { 450 $new[2] = "contains"; 451 } 452 } elsif ($conditions[$i]->[$start] =~ m/^TBD/) { 453 $new[0] = "TBD"; 454 $new[1] = $new[2] = $new[3] = ""; 455 } 456 } elsif ($API eq "IAccessible2") { 457 my $start = 0; 458 my $assert = "is"; 459 if ($conditions[$i]->[0] =~ m/^NOT/) { 460 $start = 1; 461 $assert = "isNot"; 462 } 463 if ($conditions[$i]->[$start] =~ m/^IA2_ROLE_/) { 464 $new[0] = "property"; 465 $new[1] = "role"; 466 $new[2] = $assert; 467 $new[3] = $conditions[$i]->[$start]; 468 } elsif ($conditions[$i]->[$start] =~ m/not in accessibility tree/i) { 469 @new = qw(property accessible exists false); 470 } elsif ($conditions[$i]->[$start] =~ m/^IA2_RELATION_/) { 471 $new[0] = "relation"; 472 $new[1] = $conditions[$i]->[$start]; 473 $new[2] = $assert; 474 $new[3] = $conditions[$i]->[$start+1]; 475 } elsif ($conditions[$i]->[$start] =~ m/^IA2_STATE_/) { 476 $new[0] = "property"; 477 $new[1] = "states"; 478 $new[3] = $conditions[$i]->[$start]; 479 if ($assert eq "true") { 480 $new[2] = "contains"; 481 } else { 482 $new[2] = "doesNotContain"; 483 } 484 } elsif ($conditions[$i]->[$start] =~ m/^IA2_/) { 485 $new[0] = "property"; 486 $new[1] = "states"; 487 $new[3] = $conditions[$i]->[$start]; 488 if ($assert eq "true") { 489 $new[2] = "contains"; 490 } else { 491 $new[2] = "doesNotContain"; 492 } 493 } elsif ($conditions[$i]->[$start] =~ m/(IAccessibleTable2)/i) { 494 $new[0] = "property"; 495 $new[1] = "interfaces"; 496 $new[3] = $1; 497 if ($conditions[$i]->[$start+1] ne '<shown>' 498 && $conditions[$i]->[$start+1] !~ m/true/i ) { 499 $assert = "doesNotContain"; 500 } else { 501 $assert = "contains"; 502 } 503 $new[2] = $assert; 504 } elsif ($conditions[$i]->[$start] =~ m/(.*) interface/i) { 505 $new[0] = "property"; 506 $new[1] = "interfaces"; 507 $new[3] = $1; 508 if ($conditions[$i]->[$start+1] ne '<shown>' 509 && $conditions[$i]->[$start+1] !~ m/true/i ) { 510 $assert = "doesNotContain"; 511 } else { 512 $assert = "contains"; 513 } 514 $new[2] = $assert; 515 } elsif ($conditions[$i]->[$start] =~ m/(.*)\(\)/) { 516 $new[0] = "result"; 517 $new[1] = $conditions[$i]->[$start]; 518 my $val = $conditions[$i]->[2]; 519 $val =~ s/"//g; 520 $new[3] = $conditions[$i]->[1] . ":" . $val; 521 if ($conditions[$i]->[1] eq "not exposed" 522 || $conditions[$i]->[2] eq "false") { 523 $new[2] = "doesNotContain"; 524 } else { 525 $new[2] = "contains"; 526 } 527 } elsif ($conditions[$i]->[$start] =~ m/(.*localizedExtendedRole)/) { 528 $new[0] = "result"; 529 $new[1] = $conditions[$i]->[$start]; 530 my $val = $conditions[$i]->[2]; 531 $val =~ s/"//g; 532 $new[3] = $conditions[$i]->[1] . ":" . $val; 533 if ($conditions[$i]->[1] eq "not exposed" 534 || $conditions[$i]->[2] eq "false") { 535 $new[2] = "doesNotContain"; 536 } else { 537 $new[2] = "contains"; 538 } 539 } elsif ($conditions[$i]->[$start] eq "object" || $conditions[$i]->[$start] eq "attribute" ) { 540 $new[0] = "property"; 541 $new[1] = "objectAttributes"; 542 my $val = $conditions[$i]->[2]; 543 $val =~ s/"//g; 544 $new[3] = $conditions[$i]->[1] . ":" . $val; 545 if ($conditions[$i]->[1] eq "not exposed" 546 || $conditions[$i]->[2] eq "false") { 547 $new[2] = "doesNotContain"; 548 } else { 549 $new[2] = "contains"; 550 } 551 } elsif ($conditions[$i]->[$start] =~ m/^object attribute (.*)/) { 552 my $name = $1; 553 $new[0] = "property"; 554 $new[1] = "objectAttributes"; 555 my $val = $conditions[$i]->[1]; 556 $val =~ s/"//g; 557 if ($val eq "not exposed" || $val eq "not mapped") { 558 $new[3] = $name; 559 $new[2] = "doesNotContain"; 560 } else { 561 $new[3] = $name . ":" . $val; 562 $new[2] = "contains"; 563 } 564 } else { 565 @new = @{$conditions[$i]}; 566 if ($conditions[$i]->[2] eq '<shown>') { 567 $new[2] = "contains"; 568 } 569 } 570 $conditions[$i] = \@new; 571 } elsif ($API eq "AXAPI") { 572 my $start = 0; 573 my $assert = "is"; 574 if ($conditions[$i]->[0] =~ m/^NOT/) { 575 $start = 1; 576 $assert = "isNot"; 577 } 578 if ($conditions[$i]->[$start] =~ m/^AXElementBusy/) { 579 if ($conditions[$i]->[$start+1] =~ m/yes/i) { 580 $new[3] = "true"; 581 } else { 582 $new[3] = "false"; 583 } 584 $new[0] = "property"; 585 $new[1] = $conditions[$i]->[$start]; 586 $new[2] = $assert; 587 } elsif ($conditions[$i]->[$start] =~ m/not in accessibility tree/i) { 588 @new = qw(property accessible exists false); 589 } elsif ($conditions[$i]->[$start] =~ m/^AX/) { 590 $new[0] = "property"; 591 $new[1] = $conditions[$i]->[$start]; 592 $new[2] = $assert; 593 $new[3] = $conditions[$i]->[$start+1]; 594 } elsif ($conditions[$i]->[$start] =~ m/^TBD/) { 595 $new[0] = "TBD"; 596 $new[1] = $new[2] = $new[3] = ""; 597 } else { 598 if ($conditions[$i]->[1] ne '<shown>' 599 && $conditions[$i]->[1] !~ m/true/i ) { 600 $assert = "isNot"; 601 } else { 602 $assert = "is"; 603 } 604 $new[0] = "result"; 605 $new[1] = $conditions[$i]->[0]; 606 $new[2] = $assert; 607 $new[3] = "true"; 608 } 609 } 610 if ($i == 0) { 611 if (scalar(@additional)) { 612 $rowcount += scalar(@additional); 613 } 614 $output .= "|-\n|rowspan=$rowcount|$API\n"; 615 } 616 foreach my $row (@new) { 617 $output .= "|$row\n"; 618 } 619 if (scalar(@additional)) { 620 foreach my $arow (@additional) { 621 $output .= "|-\n" ; 622 foreach my $aItem (@$arow) { 623 $output .= "|$aItem\n"; 624 } 625 } 626 } 627 } 628 } 629 $output .= "|}\n"; 630 631 return $output; 632 } 633 634 sub usage() { 635 print STDERR q(usage: make_tests.pl -f file | -w wiki_title | -s spec [-n -v -d dir ] 636 637 -s specname - the name of a spec known to the system 638 -w wiki_title - the TITLE of a wiki page with testable statements 639 -f file - the file from which to read 640 -o outFile - the file to fill with the converted wiki; defaults to STDOUT 641 642 -n - do nothing 643 -v - be verbose 644 ); 645 exit 1; 646 } 647 648 # vim: ts=2 sw=2 ai: