/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.seam.wiki.core.wikitext.engine;
import org.jboss.seam.wiki.core.model.WikiFile;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* The heart of the wiki, converts and resolves human-readable link tags from and to permanent
* links that can be stored (or read) persistently. Also resolves link texts (from stored
* link tags) to <tt>WikiLink</tt> objects, for rendering.
* <p>
* Use the supplied regular expressions to implement the methods, or parse the wiki text
* completely by hand and convert/resolve links.
*
* TODO: With a new multi-stage SeamTextParser we could remove most of this regex stuff
*
* @author Christian Bauer
*/
public interface WikiLinkResolver {
/**
* Matches known protocols, e.g. [=>http://foo.bar], which can be ignored and "resolved" as-is
*/
public static final String REGEX_KNOWN_PROTOCOL = "^(http://)|(https://)|(ftp://)|(mailto:).+$";
/**
* Matches customized protocols, e.g. [=>seamjira://123], which should be resolved and rendered
*/
public static final String REGEX_CUSTOM_PROTOCOL = "^([a-zA-Z]+)://(.+)$";
/**
* Matches WikiNode.getName() constraint.
*/
public static final String REGEX_NODE_NAME = "[a-zA-Z0-9]+[^/#\\|\\]\\[]*";
/**
* Matches URL fragments, punctuation is limited, so not all fragments are reachable.
*/
public static final String REGEX_FRAGMENT = "#[\\w\\s\\.\\,\\;\\-\\?\\!\\(\\)\\&\\/]+";
/**
* Matches "Node Name#Fragment123" with two groups.
*/
public static final String REGEX_NODE_NAME_FRAGMENT = "("+REGEX_NODE_NAME+")(" + REGEX_FRAGMENT + ")?";
/**
* Prepended to primary identifiers when links are stored, e.g. [This is a stored link=>wiki://5]
*/
public static final String REGEX_WIKI_PROTOCOL = "wiki://([0-9]+)("+REGEX_FRAGMENT+")?";
/**
* Match [GROUP1=>GROUP1], used to replace links from user input with wiki:// URLs - used
* in <tt>convertToWikiProtocol()</tt>.
*/
public static final String REGEX_WIKILINK_FORWARD =
Pattern.quote("[") + "([^" + Pattern.quote("]") + "|" + Pattern.quote("[") + "]*)" +
"=>([^" + Pattern.quote("]") + Pattern.quote("[") + "]+)" + Pattern.quote("]");
/**
* Match [GROUP1=>wiki://GROUP2], used to replace wiki:// URLs with page names
*/
public static final String REGEX_WIKILINK_REVERSE =
Pattern.quote("[") + "([^" + Pattern.quote("]") + "|" + Pattern.quote("[") + "]*)" +
"=>" + REGEX_WIKI_PROTOCOL + Pattern.quote("]");
/**
* Match "Foo Bar|Baz Brrr#Fragment" as three groups
*/
public static final String REGEX_WIKILINK_CROSSAREA = "^(" + REGEX_NODE_NAME +")"+ Pattern.quote("|") + REGEX_NODE_NAME_FRAGMENT + "$";
/**
* Replaces clear text links such as <tt>[Link description=>Target Name]</tt> in <tt>wikiText</tt> with
* <tt>[Link description=>wiki://id]</tt> strings, usually resolves the target name as a unique wiki name
* in some data store.
* The <tt>currentAreaNumber</tt> of the current document is supplied and can be used as the namespace for scoped resolving.
* <p>
* This method should be called whenever a wiki document is stored, we want to store the permanent
* identifiers of a target node. That way, the target node can be renamed and the document that links
* to that target node still contains the valid link.
* </p><p>
* Either parse the <tt>wikiText</tt> by hand to find and replace links, or use the
* <tt>REGEX_WIKILINK_FORWARD</tt> pattern which matches <tt>[GROUP1=>GROUP2]</tt>.
* Convert the target name (<tt>GROUP2</tt>) to a unique wiki name, and then to some primary
* identifier which you can lookup again in the future in a reliable fashion. <tt>GROUP1</tt> is
* the optional link description entered by the user, you need to keep this string and only replace
* <tt>GROUP2</tt> with a permanent identifier (prefixed with the <tt>wiki://</tt> protocol).
* </p><p>
* Note that cross-namespace linking should be supported, so in addition to <tt>[=>Target Name]</tt>,
* links can be entered by the user as <tt>[=>Target Area|Target Name]</tt>. To resolve these link
* texts, use <tt>REGEX_WIKILINK_CROSSAREA</tt> on the original <tt>GROUP2</tt>, which produces
* two groups. Ignore the given <tt>currentAreaNumber</tt> parameter and resolve in the target namespace entered by
* the user on the link tag.
* </p><p>
* Example pseudo code:
* </p><p>
* <pre>
* if (targetName = wikiText.match(REGEX_WIKI_LINK_FORWARD)) {
*
* if (targetNamespace, newTargetName = targetName.match(REGEX_WIKILINK_CROSSAREA) {
*
* wikiText.replace( resolveNodeId(targetNamespace, newTargetName) );
*
* } else {
* wikiText.replace( resolveNodeId(givenNamespace, targetName) );
* }
* }
* </pre>
*
* @param linkTargets This collection will be filled with <tt>WikiFile</tt> instances which are the link targets in the wiki text
* @param currentAreaNumber The currennt area useable as the namespace for scoped resolving
* @param wikiText Text with wiki markup containing [=>Target Name] links
* @return The <tt>wikiText</tt> with all <tt>[=>Target Name]<tt> links replaced with <tt>[=>wiki://id]</tt>
*/
public String convertToWikiProtocol(Set<WikiFile> linkTargets, Long currentAreaNumber, String wikiText);
/**
* Replace stored text links such as <tt>[Link description=>wiki://id]</tt> with clear text target names, so
* users can edit the link again in clear text.
* </p><p>
* Either parse by hand or use the <tt>REGEX_WIKILINK_REVERSE</tt> pattern, which matches
* <tt>[GROUP1=>(wiki://GROUP2)]. Replace with <tt>[GROUP1=>Target Name]</tt> or, if the target is not in
* the same namespace as the given <tt>area</tt> parameter, append the area:
* <tt>[GROUP1=>Target Area|Target Name]</tt>.
*
* @param currentAreaNumber The current area useable as the namespace for scoped resolving
* @param wikiText Text with wiki markup containing [=>wiki://id] links
* @return The <tt>wikiText</tt> with all <tt>[=>wiki://id]<tt> links replaced with <tt>[=>Target Name]</tt>
*/
public String convertFromWikiProtocol(Long currentAreaNumber, String wikiText);
/**
* Resolve the given <tt>linkText</tt> to an instance of <tt>WikiLink</tt> and put it in the <tt>links</tt> map.
* <p>
* The <tt>WikiLink</tt> objects are used during rendering, the rules are as follows:
* <ul>
* <li>If the <tt>linkText</tt> matches <tt>REGEX_KNOWN_PROTOCOL</tt>, don't resolve but create
* a <tt>WikiLink</tt> instance that contains <tt>url</tt>, <tt>description</tt> (same as <tt>url</tt>),
* <tt>broken=false</tt>, <tt>external=true</tt>. The <tt>url</tt> is the actual <tt>linkText</tt>, as-is.
* </li>
* <li>If the <tt>linkText</tt> matches <tt>REGEX_WIKI_PROTOCOL</tt>, resolve it and create
* a <tt>WikiLink</tt> instance that contains the resolved <tt>Node</tt> instance, the node name
* as <tt>description</tt>, no <tt>url</tt>, and <tt>external=false</tt>. If the <tt>linkText</tt>
* can't be resolved to a <tt>Node</tt>, set <tt>broken=true</tt>, a null <tt>node</tt>, and whatever
* <tt>url</tt> and <tt>description</tt> you want to render for a broken link.
* </li>
* <li>Otherwise, the <tt>linkText</tt> represents a clear text link such as <tt>Target Name</tt> or
* <tt>Target Area|TargetName</tt>, which you can resolve if you want and return a
* <tt>WikiLink</tt> instance as in the previous rule. If it can't be resolved, return a broken link
* indicator as described in the previous rule. If it has been resolved, you may indicate that the
* original document that contains this <tt>linkText</tt> should be updated in the datastore (usually
* by passing its wiki text content through <tt>convertToWikiProtocol</tt>) - set <tt>requiresUpdating=true</tt>
* on the <tt>WikiLink</tt> instance. It's the job of the client of this resolver to handle this flag
* (or to ignore it).
*</li>
* </ul>
*
* @param currentAreaNumber The current area useable as the namespace for scoped resolving
* @param links A map of all resolved <tt>WikiLink</tt> objects, keyed by <tt>linkText</tt>
* @param linkText A stored link text, such as "wiki://123" or "http://foo.bar" or "Target Area|Target Name]"
*/
public void resolveLinkText(Long currentAreaNumber, Map<String, WikiLink> links, String linkText);
public Long resolveWikiDirectoryId(Long currentAreaNumber, String linkText);
public Long resolveWikiDocumentId(Long currentAreaNumber, String linktext);
}