blob: 2677b2e3b2032fae1b1b22c07bb042bb07eae987 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 Boeing.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.osee.framework.skynet.core.linking;
import static org.eclipse.osee.framework.core.enums.DeletionFlag.INCLUDE_DELETED;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.osee.framework.core.data.BranchId;
import org.eclipse.osee.framework.core.data.TransactionToken;
import org.eclipse.osee.framework.core.enums.PresentationType;
import org.eclipse.osee.framework.jdk.core.text.change.ChangeSet;
import org.eclipse.osee.framework.jdk.core.type.HashCollection;
import org.eclipse.osee.framework.jdk.core.type.OseeCoreException;
import org.eclipse.osee.framework.jdk.core.util.Collections;
import org.eclipse.osee.framework.jdk.core.util.Strings;
import org.eclipse.osee.framework.skynet.core.artifact.Artifact;
import org.eclipse.osee.framework.skynet.core.artifact.BranchManager;
import org.eclipse.osee.framework.skynet.core.artifact.search.ArtifactQuery;
/**
* This class converts between OSEE hyperlink markers into wordML style links. <br/>
* <br/>
* <b>Example:</b>
*
* <pre>
* LinkType linkType = LinkType.OSEE_SERVER_LINK;
*
* Artifact source = ... // Artifact that contains original
* String original = ... //Doc containing osee link markers
*
* // Substitue OSEE link markers with wordML style hyperlinks requesting content to the OSEE application server
* String linkedDoc = WordMlLinkHandler.link(linkType, source, original);
*
* // Substitue wordML style hyperlinks with OSEE link markers
* String original = WordMlLinkHandler.unLink(linkType, source, linkedDoc);
* </pre>
*
* <b>Link types handled</b> <br/>
* <br/>
* <ol>
* <li><b>OSEE link:</b> This is a branch neutral marker placed in the wordML document.
*
* <pre>
* OSEE_LINK([artifact_guid])
* </pre>
*
* <li><b>Legacy style links:</b>
*
* <pre>
* &lt;w:hlink w:dest=&quot;http://[server_address]:[server_port]/Define?guid=&quot;[artifact_guid]&quot;&gt;
* &lt;w:r&gt;
* &lt;w:rPr&gt;
* &lt;w:rStyle w:val=&quot;Hyperlink&quot;/&gt;
* &lt;/w:rPr&gt;
* &lt;w:t&gt;[artifact_name]&lt;/w:t&gt;
* &lt;/w:r&gt;
* &lt;/w:hlink&gt;
* </pre>
*
* </li>
* </ol>
*
* @author Roberto E. Escobar
*/
public class WordMlLinkHandler {
private static final Matcher OSEE_LINK_PATTERN = Pattern.compile("OSEE_LINK\\((.*?)\\)", Pattern.DOTALL).matcher("");
private static final Matcher WORDML_LINK =
Pattern.compile("<w:hlink\\s+w:dest=\"(.*?)\"[^>]*?(/>|>.*?</w:hlink\\s*>)", Pattern.DOTALL).matcher("");
private static final Matcher HYPERLINK_PATTERN = Pattern.compile(
"<w:r[^>]*><w:instrText>\\s*HYPERLINK\\s+\"(.+?)\"\\s*</w:instrText></w:r>([^<]</w:t>.+?</w:fldChar></w:r>)?",
Pattern.DOTALL).matcher("");
private static final OseeLinkBuilder linkBuilder = new OseeLinkBuilder();
private static LinkType checkLinkType(LinkType value) {
return value != null ? value : LinkType.OSEE_SERVER_LINK;
}
/**
* Remove WordML hyperlinks and replace with OSEE_LINK marker. It is assumed that an unlink call will be made after a
* link call. Therefore we expect the input to have links that are recognized by this handler as identified by the
* sourceLinkType.
*
* @param source artifact that produced the string content
* @param content input
* @return processed input
*/
public static String unlink(LinkType sourceLinkType, Artifact source, String content) throws OseeCoreException {
LinkType linkType = checkLinkType(sourceLinkType);
String modified = content;
HashCollection<String, MatchRange> matchMap = parseOseeWordMLLinks(content, new HashCollection<>());
if (!matchMap.isEmpty()) {
modified = modifiedContent(linkType, source, content, matchMap, true, null);
}
return modified;
}
/**
* Replace OSEE_LINK marker or Legacy hyper-links with WordML hyperlinks.
*
* @param destLinkType type of link to produce
* @param source artifact that produced the string content
* @param content input
* @return processed input
*/
public static String link(LinkType destLinkType, Artifact source, String content, Set<String> unknownGuids) throws OseeCoreException {
return link(destLinkType, source, content, unknownGuids, PresentationType.DEFAULT_OPEN);
}
public static String link(LinkType destLinkType, Artifact source, String content, Set<String> unknownGuids, PresentationType presentationType) throws OseeCoreException {
LinkType linkType = checkLinkType(destLinkType);
String modified = content;
HashCollection<String, MatchRange> matchMap = getLinks(content, new HashCollection<>());
if (!matchMap.isEmpty()) {
modified = modifiedContent(linkType, source, content, matchMap, false, unknownGuids, presentationType);
unknownGuids.addAll(matchMap.keySet());
}
if (linkType != LinkType.OSEE_SERVER_LINK) {
// Add a bookmark to the start of the content so internal links can link later
modified = linkBuilder.getWordMlBookmark(source) + modified;
}
return modified;
}
public static HashCollection<String, MatchRange> getLinks(String content, HashCollection<String, MatchRange> errorMap) {
// Detect legacy links
HashCollection<String, MatchRange> matchMap = parseOseeWordMLLinks(content, errorMap);
// Detect new style link marker
OSEE_LINK_PATTERN.reset(content);
while (OSEE_LINK_PATTERN.find()) {
String guid = OSEE_LINK_PATTERN.group(1);
if (Strings.isValid(guid)) {
matchMap.put(guid, new MatchRange(OSEE_LINK_PATTERN.start(), OSEE_LINK_PATTERN.end()));
} else {
errorMap.put(guid, new MatchRange(WORDML_LINK.start(), WORDML_LINK.end()));
}
}
OSEE_LINK_PATTERN.reset();
return matchMap;
}
/**
* Find WordML links locations in content grouped by GUID
*
* @return locations where WordMlLinks were found grouped by GUID
*/
private static HashCollection<String, MatchRange> parseOseeWordMLLinks(String content, HashCollection<String, MatchRange> errorMap) throws OseeCoreException {
HashCollection<String, MatchRange> matchMap = new HashCollection<>();
OseeLinkParser linkParser = new OseeLinkParser();
WORDML_LINK.reset(content);
while (WORDML_LINK.find()) {
String link = WORDML_LINK.group(1);
if (Strings.isValid(link)) {
linkParser.parse(link);
String guid = linkParser.getGuid();
if (Strings.isValid(guid)) {
matchMap.put(guid, new MatchRange(WORDML_LINK.start(), WORDML_LINK.end()));
} else {
errorMap.put(linkParser.getErrLink(), new MatchRange(WORDML_LINK.start(), WORDML_LINK.end()));
}
}
}
WORDML_LINK.reset();
HYPERLINK_PATTERN.reset(content);
while (HYPERLINK_PATTERN.find()) {
String link = HYPERLINK_PATTERN.group(1);
if (Strings.isValid(link)) {
linkParser.parse(link);
String guid = linkParser.getGuid();
if (Strings.isValid(guid)) {
matchMap.put(guid, new MatchRange(HYPERLINK_PATTERN.start(), HYPERLINK_PATTERN.end()));
} else {
errorMap.put(linkParser.getErrLink(),
new MatchRange(HYPERLINK_PATTERN.start(), HYPERLINK_PATTERN.end()));
}
}
}
HYPERLINK_PATTERN.reset();
return matchMap;
}
private static List<Artifact> findArtifacts(TransactionToken transactionId, BranchId branch, boolean isHistorical, List<String> guidsFromLinks) throws OseeCoreException {
List<Artifact> artifactsFromSearch;
if (isHistorical) {
artifactsFromSearch =
ArtifactQuery.getHistoricalArtifactListFromIds(guidsFromLinks, transactionId, INCLUDE_DELETED);
} else {
artifactsFromSearch = ArtifactQuery.getArtifactListFromIds(guidsFromLinks, branch, INCLUDE_DELETED);
}
return artifactsFromSearch;
}
private static List<String> getGuidsNotFound(List<String> guidsFromLinks, List<Artifact> artifactsFound) {
Set<String> artGuids = new HashSet<>();
for (Artifact artifact : artifactsFound) {
artGuids.add(artifact.getGuid());
}
return Collections.setComplement(guidsFromLinks, artGuids);
}
private static String modifiedContent(LinkType destLinkType, Artifact source, String original, HashCollection<String, MatchRange> matchMap, boolean isUnliking, Set<String> unknown) throws OseeCoreException {
return modifiedContent(destLinkType, source, original, matchMap, isUnliking, unknown,
PresentationType.DEFAULT_OPEN);
}
private static String modifiedContent(LinkType destLinkType, Artifact source, String original, HashCollection<String, MatchRange> matchMap, boolean isUnliking, Set<String> unknown, PresentationType presentationType) throws OseeCoreException {
BranchId branch = source.getBranch();
ChangeSet changeSet = new ChangeSet(original);
List<Artifact> artifactsFromSearch = null;
List<String> guidsFromLinks = new ArrayList<>(matchMap.keySet());
artifactsFromSearch = findArtifacts(source.getTransaction(), branch, source.isHistorical(), guidsFromLinks);
if (guidsFromLinks.size() != artifactsFromSearch.size() && BranchManager.getType(branch).isMergeBranch()) {
BranchId sourceBranch = BranchManager.getParentBranch(branch);
List<String> unknownGuids = getGuidsNotFound(guidsFromLinks, artifactsFromSearch);
List<Artifact> union = new ArrayList<>();
union.addAll(findArtifacts(BranchManager.getSourceTransaction(branch), sourceBranch, source.isHistorical(),
unknownGuids));
union.addAll(artifactsFromSearch);
artifactsFromSearch = union;
}
if (guidsFromLinks.size() != artifactsFromSearch.size()) {
List<String> unknownGuids = getGuidsNotFound(guidsFromLinks, artifactsFromSearch);
if (isUnliking) {
// Ignore not found items and replace with osee marker
for (String guid : unknownGuids) {
Collection<MatchRange> matches = matchMap.getValues(guid);
for (MatchRange match : matches) {
String replaceWith = linkBuilder.getOseeLinkMarker(guid);
changeSet.replace(match.start(), match.end(), replaceWith);
}
}
} else {
// Items not found
if (!unknownGuids.isEmpty()) {
unknown.addAll(unknownGuids);
for (String guid : unknownGuids) {
for (MatchRange match : matchMap.getValues(guid)) {
String link = linkBuilder.getUnknownArtifactLink(guid, branch);
changeSet.replace(match.start(), match.end(), link);
}
}
}
}
}
// Items found in branch
for (Artifact artifact : artifactsFromSearch) {
for (MatchRange match : matchMap.getValues(artifact.getGuid())) {
String replaceWith = null;
if (isUnliking) {
replaceWith = linkBuilder.getOseeLinkMarker(artifact.getGuid());
} else {
replaceWith = linkBuilder.getWordMlLink(destLinkType, artifact, presentationType);
}
changeSet.replace(match.start(), match.end(), replaceWith);
}
}
return changeSet.applyChangesToSelf().toString();
}
public static final class MatchRange {
private final int start;
private final int end;
public MatchRange(int start, int end) {
super();
this.end = end;
this.start = start;
}
public int start() {
return start;
}
public int end() {
return end;
}
@Override
public String toString() {
return "{" + start + ", " + end + "}";
}
}
}