/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.tableeditor.settings.popup;
import static com.google.common.collect.Lists.newArrayList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.model.BaseWorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.rf.ide.core.project.RobotProjectConfig;
import org.rf.ide.core.project.RobotProjectConfig.LibraryType;
import org.rf.ide.core.project.RobotProjectConfig.ReferencedLibrary;
import org.robotframework.ide.eclipse.main.plugin.RedImages;
import org.robotframework.ide.eclipse.main.plugin.RedTheme;
import org.robotframework.ide.eclipse.main.plugin.model.RobotElement;
import org.robotframework.ide.eclipse.main.plugin.model.RobotKeywordCall;
import org.robotframework.ide.eclipse.main.plugin.model.RobotModelEvents;
import org.robotframework.ide.eclipse.main.plugin.model.RobotProject;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSetting;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSetting.SettingsGroup;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSettingsSection;
import org.robotframework.ide.eclipse.main.plugin.model.RobotSuiteFile;
import org.robotframework.ide.eclipse.main.plugin.model.cmd.settings.CreateFreshSettingCommand;
import org.robotframework.ide.eclipse.main.plugin.model.cmd.settings.DeleteSettingCommand;
import org.robotframework.ide.eclipse.main.plugin.model.cmd.settings.SetSettingArgumentCommand;
import org.robotframework.ide.eclipse.main.plugin.project.RedEclipseProjectConfigWriter;
import org.robotframework.ide.eclipse.main.plugin.project.library.LibrarySpecification;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.RobotEditorCommandsStack;
import org.robotframework.red.graphics.ImagesManager;
import org.robotframework.red.viewers.Selections;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
public class ImportLibraryComposite {
private final RobotEditorCommandsStack commandsStack;
private final FormToolkit formToolkit;
private final Shell shell;
private final RobotSuiteFile fileModel;
private TableViewer leftViewer;
private TableViewer rightViewer;
private ISelectionChangedListener leftViewerSelectionChangedListener;
private ISelectionChangedListener rightViewerSelectionChangedListener;
private final RobotProject robotProject;
private final IEventBroker eventBroker;
public ImportLibraryComposite(final RobotEditorCommandsStack commandsStack, final RobotSuiteFile fileModel,
final FormToolkit formToolkit, final Shell shell) {
this.commandsStack = commandsStack;
this.formToolkit = formToolkit;
this.fileModel = fileModel;
this.shell = shell;
robotProject = fileModel.getProject();
eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class);
}
public Composite createImportResourcesComposite(final Composite parent) {
final Composite librariesComposite = formToolkit.createComposite(parent);
GridLayoutFactory.fillDefaults()
.numColumns(4)
.margins(3, 3)
.extendedMargins(0, 0, 0, 3)
.applyTo(librariesComposite);
final Label titleLabel = formToolkit.createLabel(librariesComposite, "Libraries available in '"
+ fileModel.getProject().getName() + "' project");
titleLabel.setFont(JFaceResources.getBannerFont());
titleLabel.setForeground(formToolkit.getColors().getColor(IFormColors.TITLE));
GridDataFactory.fillDefaults().grab(true, false).span(4, 1).hint(700, SWT.DEFAULT).applyTo(titleLabel);
leftViewer = new TableViewer(librariesComposite);
leftViewer.setContentProvider(new LibrariesToImportContentProvider());
GridDataFactory.fillDefaults().span(1, 2).grab(true, true).hint(220, 250).applyTo(leftViewer.getControl());
leftViewer.setLabelProvider(new LibrariesLabelProvider());
leftViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(final DoubleClickEvent event) {
final Optional<LibrarySpecification> element = Selections.getOptionalFirstElement(
(IStructuredSelection) event.getSelection(), LibrarySpecification.class);
if (element.isPresent()) {
handleLibraryAdd((Settings) leftViewer.getInput(), newArrayList(element.get()));
}
}
});
final Composite moveBtnsComposite = formToolkit.createComposite(librariesComposite);
GridLayoutFactory.fillDefaults().numColumns(1).margins(3, 3).applyTo(moveBtnsComposite);
final Button toImported = formToolkit.createButton(moveBtnsComposite, ">>", SWT.PUSH);
toImported.setEnabled(false);
toImported.addSelectionListener(createToImportedListener());
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(toImported);
final Button fromImported = formToolkit.createButton(moveBtnsComposite, "<<", SWT.PUSH);
fromImported.setEnabled(false);
fromImported.addSelectionListener(createFromImportedListener());
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(fromImported);
rightViewer = new TableViewer(librariesComposite);
rightViewer.setContentProvider(new LibrariesAlreadyImportedContentProvider());
GridDataFactory.fillDefaults().span(1, 2).grab(true, true).hint(220, 250).applyTo(rightViewer.getControl());
rightViewer.setLabelProvider(new LibrariesLabelProvider());
rightViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(final DoubleClickEvent event) {
final Optional<LibrarySpecification> element = Selections.getOptionalFirstElement(
(IStructuredSelection) event.getSelection(), LibrarySpecification.class);
if (element.isPresent()) {
handleLibraryRemove((Settings) rightViewer.getInput(), newArrayList(element.get()));
}
}
});
final Composite newLibBtnsComposite = formToolkit.createComposite(librariesComposite);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(newLibBtnsComposite);
final Button addNewLibBtn = formToolkit.createButton(newLibBtnsComposite, "Add Library", SWT.PUSH);
addNewLibBtn.addSelectionListener(createAddNewLibListener());
GridDataFactory.fillDefaults().applyTo(addNewLibBtn);
final Button addNewExternalLibBtn = formToolkit.createButton(newLibBtnsComposite, "Add External Library", SWT.PUSH);
addNewExternalLibBtn.addSelectionListener(createAddNewExternalLibListener());
GridDataFactory.fillDefaults().applyTo(addNewExternalLibBtn);
final Button editLibPathBtn = formToolkit.createButton(newLibBtnsComposite, "Edit File Path", SWT.PUSH);
editLibPathBtn.setEnabled(false);
editLibPathBtn.addSelectionListener(createEditLibPathListener());
GridDataFactory.fillDefaults().indent(0, 10).applyTo(editLibPathBtn);
final Button editLibArgsBtn = formToolkit.createButton(newLibBtnsComposite, "Edit Arguments", SWT.PUSH);
editLibArgsBtn.setEnabled(false);
editLibArgsBtn.addSelectionListener(createEditLibArgsListener());
GridDataFactory.fillDefaults().applyTo(editLibArgsBtn);
final Label separator = formToolkit.createLabel(librariesComposite, "", SWT.SEPARATOR | SWT.HORIZONTAL);
GridDataFactory.fillDefaults().indent(0, 5).grab(false, false).span(4, 1).applyTo(separator);
final Label tooltipLabel = formToolkit.createLabel(librariesComposite,
"Choose libraries to import. Only libraries which are imported by project are available. "
+ "Edit project properties if you need other libraries", SWT.WRAP);
GridDataFactory.fillDefaults().span(4, 1).hint(500, SWT.DEFAULT).applyTo(tooltipLabel);
createLeftViewerSelectionListener(toImported);
createRightViewerSelectionListener(fromImported, editLibArgsBtn, editLibPathBtn);
return librariesComposite;
}
public ISelectionChangedListener getLeftViewerSelectionChangedListener() {
return leftViewerSelectionChangedListener;
}
public ISelectionChangedListener getRightViewerSelectionChangedListener() {
return rightViewerSelectionChangedListener;
}
public TableViewer getLeftViewer() {
return leftViewer;
}
public TableViewer getRightViewer() {
return rightViewer;
}
private SelectionListener createToImportedListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final Settings libs = (Settings) leftViewer.getInput();
final List<LibrarySpecification> specs = Selections.getElements(
(IStructuredSelection) leftViewer.getSelection(), LibrarySpecification.class);
handleLibraryAdd(libs, specs);
}
};
}
private SelectionListener createFromImportedListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final Settings libs = (Settings) rightViewer.getInput();
final List<LibrarySpecification> specs = Selections.getElements(
(IStructuredSelection) rightViewer.getSelection(), LibrarySpecification.class);
handleLibraryRemove(libs, specs);
}
};
}
private SelectionListener createAddNewLibListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final Shell newShell = new Shell(shell);
final ElementTreeSelectionDialog dialog = createAddVariableSelectionDialog(shell, null);
if (dialog.open() == Window.OK) {
final Object result = dialog.getFirstResult();
if (result != null) {
final IResource resource = (IResource) result;
final String nameWithoutExtension = ImportSettingFilePathResolver.createFileNameWithoutExtension(resource.getFullPath());
addNewLibraryToProjectConfiguration(
new Path(ImportSettingFilePathResolver.createResourceParentRelativePath(resource,
robotProject.getProject())), nameWithoutExtension);
addNewLibraryToSettingsSection(nameWithoutExtension);
}
}
newShell.dispose();
}
};
}
private SelectionListener createAddNewExternalLibListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final Shell newShell = new Shell(shell);
final FileDialog dialog = new FileDialog(newShell, SWT.OPEN);
dialog.setFilterExtensions(new String[] { "*.py", "*.*" });
final String chosenFilePath = dialog.open();
if (chosenFilePath != null) {
final IPath path = new Path(chosenFilePath);
final String nameWithoutExtension = ImportSettingFilePathResolver.createFileNameWithoutExtension(path);
addNewLibraryToProjectConfiguration(
ImportSettingFilePathResolver.createFileParentRelativePath(path,
robotProject.getProject().getLocation()), nameWithoutExtension);
addNewLibraryToSettingsSection(nameWithoutExtension);
}
newShell.dispose();
}
};
}
private void addNewLibraryToProjectConfiguration(final IPath path, final String nameWithoutExtension) {
final RobotProjectConfig config = robotProject.getRobotProjectConfig();
final ReferencedLibrary referencedLibrary = ReferencedLibrary.create(LibraryType.PYTHON, nameWithoutExtension,
path.toPortableString());
config.addReferencedLibrary(referencedLibrary);
saveConfiguration(config);
}
private void saveConfiguration(final RobotProjectConfig config) {
robotProject.clearConfiguration();
robotProject.clearKwSources();
new RedEclipseProjectConfigWriter().writeConfiguration(config, robotProject);
eventBroker.send(RobotModelEvents.ROBOT_SETTING_LIBRARY_CHANGED_IN_SUITE, "");
}
private void addNewLibraryToSettingsSection(final String nameWithoutExtension) {
if (!isLibraryAvailable(nameWithoutExtension)) {
final List<LibrarySpecification> specs = newArrayList();
final Collection<LibrarySpecification> referencedLibraries = robotProject.getReferencedLibraries().values();
for (final LibrarySpecification librarySpecification : referencedLibraries) {
if (librarySpecification != null && librarySpecification.getName().equals(nameWithoutExtension)) {
specs.add(librarySpecification);
break;
}
}
final Settings libs = (Settings) rightViewer.getInput();
handleLibraryAdd(libs, specs);
} else {
MessageDialog.openError(shell, "Error", "Given library name '" + nameWithoutExtension
+ "' already exists in current project.");
}
}
private boolean isLibraryAvailable(final String libName) {
final Settings libs = (Settings) rightViewer.getInput();
for(final LibrarySpecification spec : libs.getImportedLibraries()) {
if(spec.getName().equals(libName)) {
return true;
}
}
for(final LibrarySpecification spec : libs.getLibrariesToImport()) {
if(spec.getName().equals(libName)) {
return true;
}
}
return false;
}
private SelectionListener createEditLibPathListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final LibrarySpecification spec = Selections.getSingleElement(
(IStructuredSelection) rightViewer.getSelection(), LibrarySpecification.class);
final IPath oldPath = new Path(spec.getSecondaryKey());
final Shell newShell = new Shell(shell);
final IResource initialProjectSelection = robotProject.getProject().findMember(
oldPath + "/" + spec.getName() + ".py"); //TODO: check file extension
String nameWithoutExtension = "";
String newPath = null;
if (initialProjectSelection == null) {
final FileDialog dialog = new FileDialog(newShell, SWT.OPEN);
dialog.setFilterExtensions(new String[] { "*.py", "*.*" });
final IPath initialExtSelection = ImportSettingFilePathResolver.createFileAbsolutePath(oldPath,
robotProject.getProject());
dialog.setFilterPath(initialExtSelection.toOSString());
final String chosenFilePath = dialog.open();
if (chosenFilePath != null) {
final IPath path = new Path(chosenFilePath);
nameWithoutExtension = ImportSettingFilePathResolver.createFileNameWithoutExtension(path);
newPath = ImportSettingFilePathResolver.createFileParentRelativePath(path,
robotProject.getProject().getLocation()).toPortableString();
}
} else {
final ElementTreeSelectionDialog dialog = createAddVariableSelectionDialog(newShell, initialProjectSelection);
if (dialog.open() == Window.OK) {
final Object result = dialog.getFirstResult();
if (result != null) {
final IResource resource = (IResource) result;
nameWithoutExtension = ImportSettingFilePathResolver.createFileNameWithoutExtension(resource.getFullPath());
newPath = ImportSettingFilePathResolver.createResourceParentRelativePath(resource,
robotProject.getProject());
}
}
}
newShell.dispose();
if(newPath != null) {
if (!spec.getName().equals(nameWithoutExtension)) {
MessageDialog.openError(shell, "Error", "Libraries names are not equal.");
} else {
editLibraryInProjectConfiguration(oldPath, newPath, nameWithoutExtension);
spec.setSecondaryKey(newPath);
rightViewer.refresh();
}
}
}
};
}
private void editLibraryInProjectConfiguration(final IPath oldPath, final String newPath,
final String nameWithoutExtension) {
final RobotProjectConfig config = robotProject.getRobotProjectConfig();
final List<ReferencedLibrary> libs = config.getLibraries();
for (final ReferencedLibrary referencedLibrary : libs) {
if (referencedLibrary.getName().equals(nameWithoutExtension)
&& referencedLibrary.getPath().equals(oldPath.toPortableString())) {
referencedLibrary.setPath(newPath);
break;
}
}
saveConfiguration(config);
}
private SelectionListener createEditLibArgsListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final LibrarySpecification spec = Selections.getSingleElement(
(IStructuredSelection) rightViewer.getSelection(), LibrarySpecification.class);
final Optional<RobotSettingsSection> section = fileModel.findSection(RobotSettingsSection.class);
List<String> libArgs = newArrayList();
RobotSetting setting = null;
final List<RobotKeywordCall> settings = section.get().getImportSettings();
for (final RobotElement element : settings) {
setting = (RobotSetting) element;
if (setting.getGroup() == SettingsGroup.LIBRARIES) {
final List<String> args = setting.getArguments();
if (args != null && !args.isEmpty() && args.get(0).equals(spec.getName())) {
if (args.size() > 1) {
libArgs = args.subList(1, args.size());
}
break;
}
}
setting = null;
}
if (setting != null) {
final Shell newShell = new Shell(shell);
final ImportSettingFileArgumentsDialog dialog = new ImportSettingFileArgumentsDialog(newShell,
libArgs);
if (dialog.open() == Window.OK) {
handleEditLibraryArgs(setting, dialog.getArguments());
}
newShell.dispose();
}
}
};
}
private void handleLibraryAdd(final Settings libs, final List<LibrarySpecification> specs) {
libs.getLibrariesToImport().removeAll(specs);
libs.getImportedLibraries().addAll(specs);
final Optional<RobotSettingsSection> section = fileModel.findSection(RobotSettingsSection.class);
for (final LibrarySpecification spec : specs) {
final ArrayList<String> args = newArrayList(spec.getName());
if (spec.isRemote()) {
String host = spec.getSecondaryKey();
if (!host.startsWith("http://")) {
host = "http://" + host;
}
args.add(host);
}
commandsStack.execute(new CreateFreshSettingCommand(section.get(), "Library", args));
}
leftViewer.refresh();
rightViewer.refresh();
}
private void handleLibraryRemove(final Settings libs, final List<LibrarySpecification> specs) {
if (!doesNotContainAlwaysAccessible(specs)) {
return;
}
libs.getImportedLibraries().removeAll(specs);
libs.getLibrariesToImport().addAll(specs);
final Optional<RobotSettingsSection> section = fileModel.findSection(RobotSettingsSection.class);
final List<RobotSetting> settingsToRemove = getSettingsToRemove(section.get(), specs);
commandsStack.execute(new DeleteSettingCommand(settingsToRemove));
leftViewer.refresh();
rightViewer.refresh();
}
private void handleEditLibraryArgs(final RobotSetting setting, final List<String> newArgs) {
for (int i = 0; i < newArgs.size(); i++) {
commandsStack.execute(new SetSettingArgumentCommand(setting, i + 1, newArgs.get(i))); // set arg after keyword name
}
}
private List<RobotSetting> getSettingsToRemove(final RobotSettingsSection settingsSection,
final List<LibrarySpecification> specs) {
final List<RobotSetting> settings = newArrayList();
final List<String> specNames = Lists.transform(specs, new Function<LibrarySpecification, String>() {
@Override
public String apply(final LibrarySpecification spec) {
return spec.getName();
}
});
for (final RobotElement element : settingsSection.getImportSettings()) {
final RobotSetting setting = (RobotSetting) element;
final String name = setting.getArguments().isEmpty() ? null : setting.getArguments().get(0);
if (specNames.contains(name)) {
settings.add(setting);
}
}
return settings;
}
private boolean doesNotContainAlwaysAccessible(final List<LibrarySpecification> specs) {
for (final LibrarySpecification spec : specs) {
if (spec.isAccessibleWithoutImport()) {
return false;
}
}
return true;
}
private void createLeftViewerSelectionListener(final Button buttonToActivate) {
leftViewerSelectionChangedListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
final List<LibrarySpecification> specs = Selections.getElements(
(IStructuredSelection) event.getSelection(), LibrarySpecification.class);
buttonToActivate.setEnabled(!specs.isEmpty() && doesNotContainAlwaysAccessible(specs));
}
};
leftViewer.addSelectionChangedListener(leftViewerSelectionChangedListener);
}
private void createRightViewerSelectionListener(final Button importBtn, final Button editArgsBtn, final Button editPathBtn) {
rightViewerSelectionChangedListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
final List<LibrarySpecification> specs = Selections.getElements(
(IStructuredSelection) event.getSelection(), LibrarySpecification.class);
importBtn.setEnabled(!specs.isEmpty() && doesNotContainAlwaysAccessible(specs));
if (rightViewer.getTable().getSelectionCount() == 1) {
final LibrarySpecification spec = specs.get(0);
editPathBtn.setEnabled(spec.isReferenced());
editArgsBtn.setEnabled(!spec.isAccessibleWithoutImport());
} else {
editPathBtn.setEnabled(false);
editArgsBtn.setEnabled(false);
}
}
};
rightViewer.addSelectionChangedListener(rightViewerSelectionChangedListener);
}
protected void setInitialSelection(final RobotSetting initialSetting) {
final Settings libs = (Settings) rightViewer.getInput();
final List<LibrarySpecification> libSpecs = libs.getImportedLibraries();
if (!initialSetting.getArguments().isEmpty()) {
final String name = initialSetting.getArguments().get(0);
for (final LibrarySpecification librarySpecification : libSpecs) {
if (librarySpecification.getName().equals(name)) {
rightViewer.setSelection(new StructuredSelection(librarySpecification));
return;
}
}
}
}
private ElementTreeSelectionDialog createAddVariableSelectionDialog(final Shell shell,
final IResource initialSelection) {
final ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(shell, new WorkbenchLabelProvider(),
new BaseWorkbenchContentProvider());
dialog.setAllowMultiple(false);
dialog.setTitle("Select library file");
dialog.setMessage("Select the library file to import:");
dialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
if (initialSelection != null) {
dialog.setInitialSelection(initialSelection);
}
return dialog;
}
private static class LibrariesLabelProvider extends StyledCellLabelProvider {
@Override
public void update(final ViewerCell cell) {
final StyledString label = getStyledText(cell.getElement());
cell.setText(label.getString());
cell.setStyleRanges(label.getStyleRanges());
cell.setImage(getImage(cell.getElement()));
super.update(cell);
}
public Image getImage(final Object element) {
return ImagesManager.getImage(RedImages.getBookImage());
}
public StyledString getStyledText(final Object element) {
final LibrarySpecification spec = (LibrarySpecification) element;
final StyledString text = new StyledString(spec.getName());
if (spec.isAccessibleWithoutImport()) {
text.append(" ");
text.append("always accessible", new Styler() {
@Override
public void applyStyles(final TextStyle textStyle) {
textStyle.foreground = RedTheme.getEclipseDecorationColor();
}
});
} else if (!spec.getSecondaryKey().equals("")) {
text.append(" - " + spec.getSecondaryKey(), new Styler() {
@Override
public void applyStyles(final TextStyle textStyle) {
textStyle.foreground = RedTheme.getEclipseDecorationColor();
}
});
}
return text;
}
}
}