@Override public String getHelp(Property property) { CssModule cssModule = property.getCssModule(); if (cssModule == null) { return null; } String moduleDocBase = cssModule.getSpecificationURL(); if (moduleDocBase == null) { return null; } if ("http://www.w3.org/TR/CSS2".equals(moduleDocBase)) { // NOI18N // css2 help is treated by the legacy help resolver return null; } if (moduleDocBase.startsWith(W3C_SPEC_URL_PREFIX)) { String moduleFolderName = moduleDocBase.substring(W3C_SPEC_URL_PREFIX.length()); StringBuilder propertyUrl = new StringBuilder(); propertyUrl.append(getSpecURL()); propertyUrl.append(MODULE_ARCHIVE_PATH); propertyUrl.append(moduleFolderName); propertyUrl.append('/'); propertyUrl.append(INDEX_HTML_FILE_NAME); propertyUrl.append('#'); propertyUrl.append(property.getName()); try { URL propertyHelpURL = new URL(propertyUrl.toString()); String urlContent = URLRetriever.getURLContentAndCache(propertyHelpURL); assert urlContent != null : "null " + propertyHelpURL; // 1. find the anchor // there are some exceptions where the anchors are defined under different // ids than the property names String modifiedPropertyName = propertyNamesTranslationTable.get(property.getName()); String propertyName = modifiedPropertyName != null ? modifiedPropertyName : property.getName(); // following forms of anchors are supported: // <dfn id="property"> // <dfn id="property0"> ... sometimes the property is referred with a number suffix String elementName = "ruby".equals(cssModule.getName()) ? "a" : "dfn"; String patternImg = String.format("(?s)<%s\\s+id=['\"]?\\w*-??%s\\d?['\"]?>", elementName, propertyName); Pattern pattern = Pattern.compile(patternImg); // DOTALL mode Matcher matcher = pattern.matcher(urlContent); // 2. go backward and find h3 or h2 section start if (matcher.find()) { int sectionStart = -1; int from = matcher.start(); int state = 0; loop: for (int i = from; i > 0; i--) { char c = urlContent.charAt(i); switch (state) { case 0: if (c == '2' || c == '3') { state = 1; } break; case 1: if (c == 'h') { state = 2; } else { state = 0; } break; case 2: if (c == '<') { // found <h2 or <h3 sectionStart = i; break loop; } else { state = 0; } break; } } // 3.go forward and find next section start (h2 or h3) // note: the section end can be limited by different heading // level than was the opening heading! if (sectionStart >= 0) { // find next section Pattern sectionEndFinder = Pattern.compile("(?s)<h[23]"); // NOI18N Matcher findSectionEnd = sectionEndFinder.matcher(urlContent.subSequence(from, urlContent.length())); if (findSectionEnd.find()) { return urlContent.substring(sectionStart, from + findSectionEnd.start()); } } } else { // no pattern found, likely a bit different source LOGGER.warning( String.format( "No property anchor section pattern found for property '%s'", property.getName())); // NOI18N // strip the <style>...</style> section from the source since it causes a garbage in the // swingbrowser int styleSectionStart = urlContent.indexOf("<style type=\"text/css\">"); // NOI18N if (styleSectionStart >= 0) { final String styleEndTag = "</style>"; // NOI18N int styleSectionEnd = urlContent.indexOf(styleEndTag, styleSectionStart); if (styleSectionEnd >= 0) { StringBuilder buf = new StringBuilder(); buf.append(urlContent.subSequence(0, styleSectionStart)); buf.append( urlContent.subSequence( styleSectionEnd + styleEndTag.length(), urlContent.length())); return buf.toString(); } } return urlContent; } } catch (MalformedURLException ex) { LOGGER.log(Level.WARNING, null, ex); } } return NO_HELP_MSG; }