|
31 | 31 | import java.util.Map; |
32 | 32 | import java.util.Queue; |
33 | 33 | import java.util.Set; |
34 | | -import java.util.regex.Pattern; |
35 | 34 |
|
| 35 | +import org.w3c.epubcheck.constants.MIMEType; |
| 36 | +import org.w3c.epubcheck.url.URLFragment; |
36 | 37 | import org.w3c.epubcheck.url.URLUtils; |
37 | 38 |
|
38 | 39 | import com.adobe.epubcheck.api.EPUBLocation; |
@@ -126,6 +127,7 @@ public static final class Builder |
126 | 127 | private OPFItem item = null; |
127 | 128 | private boolean hasItemFallback = false; |
128 | 129 | private boolean hasImageFallback = false; |
| 130 | + public String mimetype; |
129 | 131 |
|
130 | 132 | public Builder url(URL url) |
131 | 133 | { |
@@ -137,6 +139,13 @@ public Builder item(OPFItem item) |
137 | 139 | { |
138 | 140 | this.url = item.getURL(); |
139 | 141 | this.item = item; |
| 142 | + this.mimetype = item.getMimeType(); |
| 143 | + return this; |
| 144 | + } |
| 145 | + |
| 146 | + public Builder mimetype(String mimetype) |
| 147 | + { |
| 148 | + this.mimetype = mimetype; |
140 | 149 | return this; |
141 | 150 | } |
142 | 151 |
|
@@ -231,8 +240,6 @@ public boolean isInSpine() |
231 | 240 | } |
232 | 241 | } |
233 | 242 |
|
234 | | - private static final Pattern REGEX_SVG_VIEW = Pattern.compile("svgView\\(.*\\)"); |
235 | | - |
236 | 243 | private final Map<URL, Resource> resources = new HashMap<URL, Resource>(); |
237 | 244 |
|
238 | 245 | private final Set<URL> undeclared = new HashSet<URL>(); |
@@ -281,7 +288,7 @@ public Optional<OPFItem> getResource(URL url) |
281 | 288 | * @param path |
282 | 289 | * the path to a publication resource |
283 | 290 | * @return an immutable {@link EnumSet} containing the types of references to |
284 | | - * {@code path}. |
| 291 | + * {@code path}. |
285 | 292 | */ |
286 | 293 | public Set<Type> getTypes(URL resource) |
287 | 294 | { |
@@ -413,9 +420,15 @@ public void checkReferences() |
413 | 420 | private void checkReference(URLReference reference) |
414 | 421 | { |
415 | 422 | Resource hostResource = resources.get(reference.location.url); |
416 | | - Resource targetResource = resources.get(reference.targetDoc); |
| 423 | + |
| 424 | + // Retrieve the Resource instance representing the targeted document |
417 | 425 | // If the resource was not declared in the manifest, |
418 | 426 | // we build a new Resource object for the data URL. |
| 427 | + Resource targetResource = resources.get(reference.targetDoc); |
| 428 | + String targetMimetype = (targetResource != null) ? targetResource.getMimeType() : ""; |
| 429 | + |
| 430 | + // Parse the URL fragment |
| 431 | + URLFragment fragment = URLFragment.parse(reference.url, targetMimetype); |
419 | 432 |
|
420 | 433 | // Check remote resources |
421 | 434 | if (container.isRemote(reference.url) |
@@ -470,15 +483,18 @@ else if (!undeclared.contains(reference.targetDoc) |
470 | 483 | return; |
471 | 484 | } |
472 | 485 |
|
473 | | - String mimetype = targetResource.getMimeType(); |
474 | | - |
475 | 486 | // Type-specific checks |
476 | 487 | switch (reference.type) |
477 | 488 | { |
478 | 489 | case HYPERLINK: |
| 490 | + if ("epubcfi".equals(fragment.getScheme())) |
| 491 | + { |
| 492 | + break; // EPUB CFI is not supported |
| 493 | + } |
479 | 494 | // if mimeType is null, we should have reported an error already |
480 | | - if (!OPFChecker.isBlessedItemType(mimetype, version) |
481 | | - && !OPFChecker.isDeprecatedBlessedItemType(mimetype) && !targetResource.hasItemFallback()) |
| 495 | + if (!OPFChecker.isBlessedItemType(targetMimetype, version) |
| 496 | + && !OPFChecker.isDeprecatedBlessedItemType(targetMimetype) |
| 497 | + && !targetResource.hasItemFallback()) |
482 | 498 | { |
483 | 499 | report.message(MessageId.RSC_010, |
484 | 500 | reference.location.context(container.relativize(reference.url))); |
@@ -494,31 +510,35 @@ else if (!undeclared.contains(reference.targetDoc) |
494 | 510 | case IMAGE: |
495 | 511 | case PICTURE_SOURCE: |
496 | 512 | case PICTURE_SOURCE_FOREIGN: |
497 | | - if (reference.url.fragment() != null && !mimetype.equals("image/svg+xml")) |
| 513 | + if ("epubcfi".equals(fragment.getScheme())) |
| 514 | + { |
| 515 | + break; // EPUB CFI is not supported |
| 516 | + } |
| 517 | + if (fragment.exists() && !MIMEType.SVG.is(targetMimetype)) |
498 | 518 | { |
499 | 519 | report.message(MessageId.RSC_009, |
500 | 520 | reference.location.context(container.relativize(reference.url))); |
501 | 521 | return; |
502 | 522 | } |
503 | 523 | // if mimeType is null, we should have reported an error already |
504 | | - if (!OPFChecker.isBlessedImageType(mimetype, version)) |
| 524 | + if (!OPFChecker.isBlessedImageType(targetMimetype, version)) |
505 | 525 | { |
506 | 526 | if (version == EPUBVersion.VERSION_3 && reference.type == Type.PICTURE_SOURCE) |
507 | 527 | { |
508 | 528 | report.message(MessageId.MED_007, reference.location, |
509 | | - container.relativize(reference.targetDoc), mimetype); |
| 529 | + container.relativize(reference.targetDoc), targetMimetype); |
510 | 530 | return; |
511 | 531 | } |
512 | 532 | else if (reference.type == Type.IMAGE && !targetResource.hasImageFallback()) |
513 | 533 | { |
514 | 534 | report.message(MessageId.MED_003, reference.location, |
515 | | - container.relativize(reference.targetDoc), mimetype); |
| 535 | + container.relativize(reference.targetDoc), targetMimetype); |
516 | 536 | } |
517 | 537 | } |
518 | 538 | break; |
519 | 539 | case SEARCH_KEY: |
520 | 540 | // TODO update when we support EPUB CFI |
521 | | - if ((reference.url.fragment() == null || !reference.url.fragment().startsWith("epubcfi(")) |
| 541 | + if ((!fragment.exists() || !"epubcfi".equals(fragment.getScheme())) |
522 | 542 | && !targetResource.isInSpine()) |
523 | 543 | { |
524 | 544 | report.message(MessageId.RSC_021, reference.location, |
@@ -527,7 +547,7 @@ else if (reference.type == Type.IMAGE && !targetResource.hasImageFallback()) |
527 | 547 | } |
528 | 548 | break; |
529 | 549 | case STYLESHEET: |
530 | | - if (reference.url.fragment() != null) |
| 550 | + if (fragment.exists()) |
531 | 551 | { |
532 | 552 | report.message(MessageId.RSC_013, |
533 | 553 | reference.location.context(container.relativize(reference.url))); |
@@ -551,7 +571,7 @@ else if (reference.type == Type.IMAGE && !targetResource.hasImageFallback()) |
551 | 571 | case SVG_CLIP_PATH: |
552 | 572 | case SVG_PAINT: |
553 | 573 | case SVG_SYMBOL: |
554 | | - if (reference.url.fragment() == null) |
| 574 | + if (!fragment.exists()) |
555 | 575 | { |
556 | 576 | report.message(MessageId.RSC_015, reference.location.context(reference.url)); |
557 | 577 | return; |
@@ -562,32 +582,32 @@ else if (reference.type == Type.IMAGE && !targetResource.hasImageFallback()) |
562 | 582 | } |
563 | 583 |
|
564 | 584 | // Fragment integrity checks |
565 | | - String fragment = reference.url.fragment(); |
566 | | - if (fragment != null && !fragment.isEmpty()) |
| 585 | + if (fragment.exists() && !fragment.isEmpty()) |
567 | 586 | { |
568 | 587 | // EPUB CFI |
569 | | - if (fragment.startsWith("epubcfi(")) |
| 588 | + if ("epubcfi".equals(fragment.getScheme())) |
570 | 589 | { |
| 590 | + // FIXME HOT should warn if in MO |
571 | 591 | // FIXME epubcfi currently not supported (see issue 150). |
572 | 592 | return; |
573 | 593 | } |
574 | 594 | // Media fragments in Data Navigation Documents |
575 | | - else if (fragment.contains("=") && hostResource != null && hostResource.hasItem() |
| 595 | + else if (fragment.isMediaFragment() && hostResource != null && hostResource.hasItem() |
576 | 596 | && hostResource.getItem().getProperties() |
577 | 597 | .contains(PackageVocabs.ITEM_VOCAB.get(PackageVocabs.ITEM_PROPERTIES.DATA_NAV))) |
578 | 598 | { |
579 | 599 | // Ignore, |
580 | 600 | return; |
581 | 601 | } |
582 | | - // SVG view fragments are ignored |
583 | | - else if (mimetype.equals("image/svg+xml") && REGEX_SVG_VIEW.matcher(fragment).matches()) |
| 602 | + // Non-ID-based fragments are ignored |
| 603 | + else if (fragment.getId().isEmpty()) |
584 | 604 | { |
585 | 605 | return; |
586 | 606 | } |
587 | 607 | // Fragment Identifier (by default) |
588 | 608 | else if (!container.isRemote(reference.targetDoc)) |
589 | 609 | { |
590 | | - ID anchor = targetResource.ids.get(fragment); |
| 610 | + ID anchor = targetResource.ids.get(fragment.getId()); |
591 | 611 | if (anchor == null) |
592 | 612 | { |
593 | 613 | report.message(MessageId.RSC_012, reference.location.context(reference.url.toString())); |
@@ -674,7 +694,8 @@ private void checkReadingOrder(Queue<URLReference> references, int lastSpinePosi |
674 | 694 | } |
675 | 695 |
|
676 | 696 | // check that the fragment is in document order |
677 | | - int targetAnchorPosition = res.getIDPosition(ref.url.fragment()); |
| 697 | + URLFragment fragment = URLFragment.parse(ref.url, res.getMimeType()); |
| 698 | + int targetAnchorPosition = res.getIDPosition(fragment.getId()); |
678 | 699 | if (targetAnchorPosition < lastAnchorPosition) |
679 | 700 | { |
680 | 701 | String orderContext = LocalizedMessages.getInstance(locale).getSuggestion(MessageId.NAV_011, |
|