| /*=============================================================================# |
| # Copyright (c) 2010, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.rhelp.core.update; |
| |
| import static org.eclipse.statet.internal.rhelp.core.RHelpCoreInternals.DEBUG; |
| import static org.eclipse.statet.internal.rhelp.core.index.RHelpHtmlUtils.HR_LINE_PATTERN; |
| import static org.eclipse.statet.internal.rhelp.core.index.RHelpHtmlUtils.HR_PREFIX; |
| import static org.eclipse.statet.rhelp.core.RHelpCore.BUNDLE_ID; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.InputStreamReader; |
| import java.nio.file.Path; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ArrayBlockingQueue; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.InfoStatus; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.status.WarningStatus; |
| |
| import org.eclipse.statet.internal.rhelp.core.RHelpWebapp; |
| import org.eclipse.statet.internal.rhelp.core.index.AbortIndexOperationException; |
| import org.eclipse.statet.internal.rhelp.core.index.RDocResource; |
| import org.eclipse.statet.internal.rhelp.core.index.REnvIndexWriter; |
| import org.eclipse.statet.internal.rhelp.core.index.REnvIndexWriter.RdItem; |
| import org.eclipse.statet.rhelp.core.REnvHelpConfiguration; |
| import org.eclipse.statet.rhelp.core.RHelpManager; |
| import org.eclipse.statet.rj.data.RCharacterStore; |
| import org.eclipse.statet.rj.data.RDataUtils; |
| import org.eclipse.statet.rj.data.RList; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RStore; |
| import org.eclipse.statet.rj.data.RVector; |
| import org.eclipse.statet.rj.renv.core.BasicRPkgDescription; |
| import org.eclipse.statet.rj.renv.core.RLibLocation; |
| import org.eclipse.statet.rj.renv.core.RNumVersion; |
| import org.eclipse.statet.rj.renv.core.RPkgBuilt; |
| import org.eclipse.statet.rj.renv.core.RPkgCompilation; |
| import org.eclipse.statet.rj.renv.core.RPkgDescription; |
| import org.eclipse.statet.rj.renv.runtime.RLibLocationInfo; |
| import org.eclipse.statet.rj.renv.runtime.RPkgManager; |
| import org.eclipse.statet.rj.renv.runtime.RPkgManagerDataset; |
| import org.eclipse.statet.rj.renv.runtime.RuntimeRLibPaths; |
| import org.eclipse.statet.rj.services.FunctionCall; |
| import org.eclipse.statet.rj.services.RService; |
| |
| |
| /** |
| * Updates the R environment (R help keywords, R help index). |
| * Uses the RService interface to read the data. |
| */ |
| @NonNullByDefault |
| public abstract class REnvIndexUpdater { |
| |
| |
| private static final String PKG_DESCR_FNAME= "rj:::rhelp.loadPkgDescr"; //$NON-NLS-1$ |
| private static final int PKG_DESCR_LENGTH= 7; |
| private static final int PKG_DESCR_IDX_VERSION= 0; |
| private static final int PKG_DESCR_IDX_TITLE= 1; |
| private static final int PKG_DESCR_IDX_DESCRIPTION= 2; |
| private static final int PKG_DESCR_IDX_AUTHOR= 3; |
| private static final int PKG_DESCR_IDX_MAINTAINER= 4; |
| private static final int PKG_DESCR_IDX_URL= 5; |
| private static final int PKG_DESCR_IDX_BUILT= 6; |
| |
| private static final String PKG_RD_FNAME= "rj:::rhelp.loadPkgRd"; //$NON-NLS-1$ |
| |
| |
| private static String checkNA2Null(final String s) { |
| return (s != null && !s.equals("NA") && s.length() > 0) ? s : null; //$NON-NLS-1$ |
| } |
| |
| |
| private static class PkgTask { |
| |
| |
| final String name; |
| final @Nullable RNumVersion version; |
| final @Nullable String built; |
| final RLibLocation libLocation; |
| final RLibLocationInfo libLocationInfo; // for error messages |
| |
| RVector<RCharacterStore> rDescr; |
| |
| RList rRd; |
| |
| |
| public PkgTask(final String name, final RNumVersion version, final String built, |
| final RLibLocation libLocation, final RLibLocationInfo libLocationInfo) { |
| this.name= name.intern(); |
| this.version= version; |
| this.built= built; |
| this.libLocation= libLocation; |
| this.libLocationInfo= libLocationInfo; |
| } |
| |
| @SuppressWarnings("null") |
| private PkgTask(final String signal) { |
| this.name= signal; |
| this.version= null; |
| this.built= null; |
| this.libLocation= null; |
| this.libLocationInfo= null; |
| } |
| |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb= new StringBuilder(this.name); |
| sb.append(" " + "(version= ").append((this.version != null) ? this.version : "<unknown>"); |
| sb.append(')'); |
| if (this.libLocationInfo != null) { |
| sb.append(" in lib= '").append(this.libLocationInfo.getDirectoryRPath()).append('\''); |
| } |
| return sb.toString(); |
| } |
| |
| } |
| |
| private static final PkgTask STOP_OK= new PkgTask("STOP_OK"); |
| private static final PkgTask STOP_CANCEL= new PkgTask("STOP_CANCEL"); |
| |
| |
| protected abstract class IndexJob { |
| |
| |
| private final BlockingQueue<PkgTask> queue= new ArrayBlockingQueue<>(4); |
| |
| private volatile @Nullable Exception exception; |
| |
| private int count; |
| private int queueCumCount; |
| |
| |
| public IndexJob() { |
| } |
| |
| |
| protected abstract void cancel(); |
| protected abstract void join(); |
| |
| |
| private void add(final PkgTask task, final ProgressMonitor m) throws Exception { |
| while (true) { |
| try { |
| if (this.exception != null) { |
| throw this.exception; |
| } |
| |
| this.count++; |
| this.queueCumCount+= this.queue.size(); |
| |
| this.queue.put(task); |
| break; |
| } |
| catch (final InterruptedException e) { |
| if (m.isCanceled()) { |
| cancel(); |
| throw new StatusException(Status.CANCEL_STATUS); |
| } |
| } |
| } |
| } |
| |
| private void finish(final ProgressMonitor m) throws StatusException { |
| while (true) { |
| try { |
| this.queue.put(STOP_OK); |
| break; |
| } |
| catch (final InterruptedException e) {} |
| } |
| join(); |
| } |
| |
| private void cancel(final ProgressMonitor m) { |
| cancel(); |
| while (true) { |
| try { |
| this.queue.put(STOP_CANCEL); |
| break; |
| } |
| catch (final InterruptedException e) {} |
| } |
| join(); |
| } |
| |
| public Status run(final ProgressMonitor monitor) { |
| try { |
| PkgTask task= null; |
| while (true) { |
| try { |
| task= this.queue.take(); |
| if (task == STOP_OK) { |
| if (DEBUG) { |
| REnvIndexUpdater.this.index.log(new InfoStatus(BUNDLE_ID, |
| String.format("Fill level of queue: mean= %1.2f.", |
| (double) this.queueCumCount / this.count ))); |
| } |
| return Status.OK_STATUS; |
| } |
| if (task == STOP_CANCEL) { |
| return Status.CANCEL_STATUS; |
| } |
| |
| final RPkgDescription pkgDescription= createDescription(task); |
| REnvIndexUpdater.this.index.beginPackage(pkgDescription); |
| processRdData(pkgDescription.getName(), task.rRd); |
| } |
| catch (final InterruptedException e) { |
| // continue, monitor is checked |
| } |
| catch (final AbortIndexOperationException e) { |
| this.exception= e; |
| this.queue.clear(); |
| return Status.CANCEL_STATUS; |
| } |
| catch (final Exception e) { |
| REnvIndexUpdater.this.index.log(new ErrorStatus(BUNDLE_ID, |
| String.format("An error occurred when indexing data for package: %1$s", |
| task ), |
| e )); |
| } |
| finally { |
| try { |
| REnvIndexUpdater.this.index.endPackage(); |
| } |
| catch (final Exception e) { |
| this.exception= e; |
| this.queue.clear(); |
| return Status.CANCEL_STATUS; |
| } |
| } |
| |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| } |
| } |
| catch (final Exception e) { |
| this.exception= e; |
| this.queue.clear(); |
| return Status.CANCEL_STATUS; |
| } |
| } |
| |
| } |
| |
| |
| private final REnvHelpConfiguration rEnvConfig; |
| |
| private final StringBuilder tempBuilder1= new StringBuilder(65536); |
| private final StringBuilder tempBuilder2= new StringBuilder(1024); |
| |
| private final REnvIndexWriter index; |
| |
| private final RPkgManager rPkgManager; |
| |
| |
| public REnvIndexUpdater(final REnvHelpConfiguration rEnvConfig, |
| final RHelpManager rHelpManager, final RPkgManager rPkgManager) { |
| this.rEnvConfig= rEnvConfig; |
| this.index= new REnvIndexWriter(rEnvConfig, rHelpManager); |
| this.rPkgManager= rPkgManager; |
| } |
| |
| |
| protected RPkgManager getRPkgManager() { |
| return this.rPkgManager; |
| } |
| |
| |
| public Status update(final RService r, final boolean reset, |
| final @Nullable Map<String, String> rEnvSharedProperties, |
| final ProgressMonitor m) { |
| m.setWorkRemaining(1 + 8 + 1); |
| try { |
| this.index.beginBatch(reset); |
| |
| final String docDir= checkNA2Null(RDataUtils.checkSingleChar( |
| r.evalData("R.home(\"doc\")", m) )); //$NON-NLS-1$ |
| this.index.setDocDir(docDir, (docDir != null) ? toLocalPath(docDir, r, m) : null); |
| this.index.addManuals(RDocResource.R_MANUALS); |
| this.index.addMiscResources(RDocResource.R_MISCS); |
| |
| this.index.setREnvSharedProperties(rEnvSharedProperties); |
| |
| long tKeywords= System.nanoTime(); |
| loadKeywords(r, m.newSubMonitor(1)); |
| tKeywords= System.nanoTime() - tKeywords; |
| |
| m.checkCanceled(); |
| |
| long tPackages= System.nanoTime(); |
| loadPackages(r, m.newSubMonitor(8)); |
| tPackages= System.nanoTime() - tPackages; |
| |
| if (DEBUG) { |
| this.index.log(new InfoStatus(BUNDLE_ID, |
| String.format("Required time for update: keywords= %1$sms, packages= %2$sms.", |
| tKeywords / 1_000_000, tPackages / 1_000_000 ))); |
| } |
| |
| final Status status= this.index.endBatch(); |
| if (status != null && status.getSeverity() >= Status.ERROR) { |
| return new WarningStatus(BUNDLE_ID, |
| "The R environment index could not be completely updated.", |
| new StatusException(status) ); |
| } |
| return new InfoStatus(BUNDLE_ID, |
| "The R environment index was updated successfully." ); |
| } |
| catch (final StatusException e) { |
| if (e.getStatus().getSeverity() == Status.CANCEL) { |
| this.index.cancel(); |
| return e.getStatus(); |
| } |
| this.index.log(new ErrorStatus(BUNDLE_ID, |
| "An error occurred when updating the R environment.", |
| e )); |
| final Status status= this.index.cancel(); |
| return new ErrorStatus(BUNDLE_ID, |
| "The R environment could not be updated.", |
| (status != null) ? new StatusException(status) : null ); |
| } |
| catch (final Exception e) { |
| this.index.log(new ErrorStatus(BUNDLE_ID, |
| "An error occurred when updating the R environment.", |
| e )); |
| final Status status= this.index.cancel(); |
| return new ErrorStatus(BUNDLE_ID, |
| "The R environment could not be updated.", |
| (status != null) ? new StatusException(status) : null ); |
| } |
| } |
| |
| private void loadKeywords(final RService r, final ProgressMonitor m) throws StatusException { |
| final String docDir= this.index.getDocDir(); |
| if (docDir == null) { |
| return; |
| } |
| m.beginTask("Loading R help keywords...", 10 + 10); |
| Exception errorCause= null; |
| try { |
| final byte[] bytes= r.downloadFile(docDir + "/KEYWORDS.db", 0, m.newSubMonitor(10)); |
| final BufferedReader reader= new BufferedReader(new InputStreamReader( |
| new ByteArrayInputStream(bytes), "UTF-8")); //$NON-NLS-1$ |
| String line; |
| |
| this.tempBuilder1.setLength(0); |
| while ((line= reader.readLine()) != null) { |
| if (DEBUG) { |
| this.tempBuilder1.append(line); |
| this.tempBuilder1.append('\n'); |
| } |
| int idx= line.indexOf('#'); |
| if (idx >= 0) { |
| line= line.substring(0, idx); |
| } |
| idx= line.indexOf(':'); |
| if (idx < 0) { |
| continue; |
| } |
| final String descr= new String(line.substring(idx + 1).trim()); |
| line= line.substring(0, idx); |
| this.index.addDefaultKeyword(line.split("\\|"), descr); //$NON-NLS-1$ |
| } |
| m.addWorked(10); |
| |
| return; |
| } |
| catch (final StatusException e) { |
| if (e.getStatus().getSeverity() == Status.CANCEL) { |
| throw e; |
| } |
| errorCause= e; |
| } |
| catch (final Exception e) { |
| errorCause= e; |
| } |
| finally { |
| if (DEBUG) { |
| this.tempBuilder1.insert(0, "Read KEYWORDS.db file:\n<FILE>\n"); |
| this.tempBuilder1.append("</FILE>\n"); |
| this.index.log(new InfoStatus(BUNDLE_ID, this.tempBuilder1.toString())); |
| } |
| } |
| this.index.log(new ErrorStatus(BUNDLE_ID, |
| "An error occurred when loading the keyword list.", |
| errorCause )); |
| } |
| |
| private void loadPackages(final RService r, final ProgressMonitor m) throws StatusException { |
| m.beginTask("Loading R package help.", 8 + 1); |
| Exception errorCause= null; |
| IndexJob job= null; |
| try { |
| final RPkgManagerDataset rPkgDataset= this.rPkgManager.getDataset(RPkgManager.INSTALLED, |
| r, m ); |
| |
| job= scheduleIndexJob( |
| String.format("Update R help index for '%1$s'", this.rEnvConfig.getName())); |
| |
| final RuntimeRLibPaths rLibPaths= (RuntimeRLibPaths) rPkgDataset.getRLibPaths(); |
| final RPkgCompilation<? extends RPkgBuilt> installed= rPkgDataset.getInstalled(); |
| |
| final ProgressMonitor mPkgs= m.newSubMonitor(8); |
| final List<String> names= installed.getNames(); |
| for (int i= 0; i < names.size(); i++) { |
| mPkgs.setWorkRemaining(2 * (names.size() - i)); |
| |
| final RPkgBuilt pkgInfo= installed.getFirst(names.get(i)); |
| |
| if (this.index.checkPackage(pkgInfo.getName(), |
| pkgInfo.getVersion(), pkgInfo.getBuilt(), |
| pkgInfo.getLibLocation() )) { |
| continue; |
| } |
| |
| mPkgs.checkCanceled(); |
| |
| mPkgs.beginSubTask(String.format("Loading data for package '%1$s'...", pkgInfo.getName())); |
| |
| try { |
| final RLibLocationInfo libLocationInfo= rLibPaths.getInfo(pkgInfo.getLibLocation()); |
| if (libLocationInfo == null) { |
| throw new StatusException(new ErrorStatus(BUNDLE_ID, |
| String.format("Failed to resolve library location '%1$s'.", |
| pkgInfo.getLibLocation() ))); |
| } |
| final PkgTask task= new PkgTask(pkgInfo.getName(), pkgInfo.getVersion(), pkgInfo.getBuilt(), |
| pkgInfo.getLibLocation(), libLocationInfo ); |
| { final FunctionCall call= r.createFunctionCall(PKG_DESCR_FNAME); |
| call.addChar("lib", libLocationInfo.getDirectoryRPath()); //$NON-NLS-1$ |
| call.addChar("name", pkgInfo.getName()); //$NON-NLS-1$ |
| task.rDescr= RDataUtils.checkRCharVector(call.evalData(mPkgs.newSubMonitor(1))); |
| } |
| { final FunctionCall call= r.createFunctionCall(PKG_RD_FNAME); |
| call.addChar("lib", libLocationInfo.getDirectoryRPath()); //$NON-NLS-1$ |
| call.addChar("name", pkgInfo.getName()); //$NON-NLS-1$ |
| task.rRd= RDataUtils.checkRList(call.evalData(mPkgs.newSubMonitor(1))); |
| } |
| job.add(task, mPkgs); |
| } |
| catch (final StatusException e) { // only core exceptions! |
| if (e.getStatus().getSeverity() == Status.CANCEL) { |
| throw e; |
| } |
| this.index.log(new ErrorStatus(BUNDLE_ID, |
| String.format("An error occurred when loading data for package '%1$s' in '%2$s'.", |
| pkgInfo.getName(), pkgInfo.getLibLocation() ), |
| e )); |
| } |
| } |
| |
| m.beginSubTask("Finishing index of help..."); |
| job.finish(m.newSubMonitor(2)); |
| job= null; |
| |
| |
| return; |
| } |
| catch (final StatusException e) { |
| if (e.getStatus().getSeverity() == Status.CANCEL) { |
| throw e; |
| } |
| errorCause= e; |
| } |
| catch (final Exception e) { |
| errorCause= e; |
| } |
| finally { |
| if (job != null) { |
| job.cancel(m); |
| } |
| } |
| throw new StatusException(new ErrorStatus(BUNDLE_ID, |
| "An error occurred when loading the package data.", |
| errorCause )); |
| } |
| |
| protected abstract IndexJob scheduleIndexJob(final String name); |
| |
| |
| private static final Pattern URL_SPLIT_PATTERN= Pattern.compile("(?:,|\\s)+"); //$NON-NLS-1$ |
| |
| private RPkgDescription createDescription(final PkgTask task) throws Exception { |
| final RCharacterStore data= RDataUtils.checkLengthEqual(task.rDescr.getData(), PKG_DESCR_LENGTH); |
| |
| final RNumVersion version; |
| { final String versionString= RDataUtils.checkValue(data, PKG_DESCR_IDX_VERSION); |
| final RNumVersion taskVersion= task.version; |
| if (taskVersion != null) { |
| if (!taskVersion.toString().equals(versionString)) { |
| throw new Exception( |
| String.format("Unexpected package version: expected=%1$s, found=%2$s", |
| taskVersion, versionString )); |
| } |
| version= taskVersion; |
| } |
| else { |
| version= RNumVersion.create(versionString); |
| } |
| } |
| final String built; |
| { final String builtString= RDataUtils.checkValue(data, PKG_DESCR_IDX_BUILT); |
| final String taskBuilt= task.built; |
| if (taskBuilt != null) { |
| if (!taskBuilt.equals(builtString)) { |
| throw new Exception( |
| String.format("Unexpected package built: expected=%1$s, found=%2$s", |
| taskBuilt, builtString )); |
| } |
| built= taskBuilt; |
| } |
| else { |
| built= builtString; |
| } |
| } |
| final ImList<String> urls; |
| { String urlString= data.get(PKG_DESCR_IDX_URL); |
| if (urlString != null && !(urlString= urlString.strip()).isEmpty()) { |
| urls= ImCollections.newList(URL_SPLIT_PATTERN.split(urlString)); |
| } |
| else { |
| urls= ImCollections.emptyList(); |
| } |
| } |
| return new BasicRPkgDescription(task.name, |
| version, |
| RDataUtils.getValue(data, PKG_DESCR_IDX_TITLE, ""), //$NON-NLS-1$ |
| RDataUtils.getValue(data, PKG_DESCR_IDX_DESCRIPTION, ""), //$NON-NLS-1$ |
| data.get(PKG_DESCR_IDX_AUTHOR), |
| data.get(PKG_DESCR_IDX_MAINTAINER), |
| urls, |
| built, task.libLocation ); |
| } |
| |
| private void processRdData(final String pkgName, final RList pkgList) throws Exception { |
| for (int j= 0; j < pkgList.getLength(); j++) { |
| final RObject rdObj= pkgList.get(j); |
| if (rdObj.getRClassName().equals("RdData")) { //$NON-NLS-1$ |
| final RList rdData= (RList) rdObj; |
| final RdItem rdItem= new RdItem(pkgName, pkgList.getName(j)); |
| { final RStore<?> store= rdData.get("title").getData(); //$NON-NLS-1$ |
| if (!store.isNA(0)) { |
| rdItem.setTitle(store.getChar(0)); |
| } |
| } |
| { final RStore<?> store= rdData.get("topics").getData(); //$NON-NLS-1$ |
| for (int k= 0; k < store.getLength(); k++) { |
| if (!store.isNA(k)) { |
| final String alias= store.getChar(k).trim(); |
| if (alias.length() > 0) { |
| rdItem.addTopic(alias); |
| } |
| } |
| } |
| } |
| { final RStore<?> store= rdData.get("keywords").getData(); //$NON-NLS-1$ |
| for (int k= 0; k < store.getLength(); k++) { |
| if (!store.isNA(k)) { |
| final String keyword= store.getChar(k).trim(); |
| if (keyword.length() > 0) { |
| rdItem.addKeyword(keyword); |
| } |
| } |
| } |
| } |
| { final RStore<?> store= rdData.get("concepts").getData(); //$NON-NLS-1$ |
| for (int k= 0; k < store.getLength(); k++) { |
| if (!store.isNA(k)) { |
| final String concept= store.getChar(k).trim(); |
| if (concept.length() > 0) { |
| rdItem.addConcept(concept); |
| } |
| } |
| } |
| } |
| { final RStore<?> store= rdData.get("HTML").getData(); //$NON-NLS-1$ |
| if (store != null && store.getStoreType() == RStore.CHARACTER) { |
| rdItem.setHtml(processHtml((RCharacterStore) store)); |
| } |
| } |
| this.index.add(rdItem); |
| } |
| } |
| } |
| |
| @SuppressWarnings("nls") |
| private String processHtml(final RCharacterStore store) { |
| this.tempBuilder1.setLength(0); |
| this.tempBuilder2.setLength(0); |
| int length= 0; |
| for (int i= 0; i < store.getLength(); i++) { |
| if (!store.isNA(i)) { |
| length+= store.getChar(i).length() + 2; |
| } |
| } |
| length+= 300; |
| this.tempBuilder2.ensureCapacity(length); |
| |
| int topIndex= -1; |
| boolean inExamples= false; |
| this.tempBuilder2.append("<div class=\"toc\"><ul>"); |
| for (int i= 0; i < store.getLength(); i++) { |
| Matcher matcher; |
| if (!store.isNA(i)) { |
| String line= store.getChar(i); |
| if (topIndex == -1) { |
| if (line.startsWith("<table ")) { |
| this.tempBuilder1.append("<table class=\"header\" "); |
| line= line.substring(7); |
| } |
| else if (line.startsWith("<h2>")) { |
| topIndex= this.tempBuilder1.length(); |
| this.tempBuilder1.append("<h2 id=\"top\">"); |
| line= line.substring(4); |
| } |
| } |
| else if (topIndex >= 0 && line.length() > 10) { |
| if (line.startsWith("<h3>")) { |
| if (inExamples) { |
| this.tempBuilder1.append(RHelpWebapp.HTML_END_EXAMPLES); |
| inExamples= false; |
| } |
| switch (line.charAt(4) - line.charAt(6)) { |
| case ('D' - 's'): |
| if (line.equals("<h3>Description</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#description\"><span class=\"mnemonic\">D</span>escription</a></li>"); //$NON-NLS-1$ |
| line= "<h3 id=\"description\">Description</h3>"; |
| break; |
| } |
| break; |
| case ('U' - 'a'): |
| if (line.equals("<h3>Usage</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#usage\"><span class=\"mnemonic\">U</span>sage</a></li>"); //$NON-NLS-1$ |
| line= "<h3 id=\"usage\">Usage</h3>"; |
| break; |
| } |
| break; |
| case ('A' - 'g'): |
| if (line.equals("<h3>Arguments</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#arguments\"><span class=\"mnemonic\">A</span>rguments</a></li>"); //$NON-NLS-1$ |
| line= "<h3 id=\"arguments\">Arguments</h3>"; |
| break; |
| } |
| break; |
| case ('D' - 't'): |
| if (line.equals("<h3>Details</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#details\">Deta<span class=\"mnemonic\">i</span>ls</a></li>"); |
| line= "<h3 id=\"details\">Details</h3>"; |
| break; |
| } |
| break; |
| case ('V' - 'l'): |
| if (line.equals("<h3>Value</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#value\"><span class=\"mnemonic\">V</span>alue</a></li>"); |
| line= "<h3 id=\"value\">Value</h3>"; |
| break; |
| } |
| break; |
| case ('A' - 't'): |
| if (line.equals("<h3>Author(s)</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#authors\">Auth<span class=\"mnemonic\">o</span>r(s)</a></li>"); |
| line= "<h3 id=\"authors\">Author(s)</h3>"; |
| break; |
| } |
| break; |
| case ('R' - 'f'): |
| if (line.equals("<h3>References</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#references\"><span class=\"mnemonic\">R</span>eferences</a></li>"); |
| line= "<h3 id=\"references\">References</h3>"; |
| break; |
| } |
| break; |
| case ('E' - 'a'): |
| if (line.equals("<h3>Examples</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#examples\"><span class=\"mnemonic\">E</span>xamples</a></li>"); |
| line= "<h3 id=\"examples\">Examples</h3>" + RHelpWebapp.HTML_BEGIN_EXAMPLES; |
| inExamples= true; |
| break; |
| } |
| break; |
| case ('S' - 'e'): |
| if (line.equals("<h3>See Also</h3>")) { |
| this.tempBuilder2.append("<li><a href=\"#seealso\"><span class=\"mnemonic\">S</span>ee Also</a></li>"); |
| line= "<h3 id=\"seealso\">See Also</h3>"; |
| break; |
| } |
| break; |
| } |
| } |
| else if (line.startsWith(HR_PREFIX) |
| && (matcher= HR_LINE_PATTERN.matcher(line)).find() ) { |
| if (inExamples) { |
| this.tempBuilder1.append(RHelpWebapp.HTML_END_EXAMPLES); |
| inExamples= false; |
| } |
| // if (line.startsWith("<hr><div align=\"center\">[Package <em>")) { |
| // this.tempBuilder1.append("<hr/><div class=\"toc\"><ul><li><a href=\"#top\">Top</a></li></ul></div>"); |
| // } |
| this.tempBuilder1.append("<hr/>"); |
| line= line.substring(matcher.end() - matcher.start()); |
| } |
| } |
| this.tempBuilder1.append(line); |
| this.tempBuilder1.append('\r'); |
| this.tempBuilder1.append('\n'); |
| } |
| } |
| if (topIndex >= 0) { |
| this.tempBuilder2.append("</ul></div>"); |
| this.tempBuilder1.insert(topIndex, this.tempBuilder2); |
| } |
| return this.tempBuilder1.toString(); |
| } |
| |
| protected Path toLocalPath(final String rPath, |
| final RService r, final ProgressMonitor m) throws StatusException { |
| return Path.of(rPath); |
| } |
| |
| } |