package folioxml.translation; import folioxml.core.InvalidMarkupException; import folioxml.css.CssClassCleaner; import folioxml.folio.FolioToken; import folioxml.slx.SlxToken; import java.util.regex.Pattern; /** * Doesn't deal with Popup links (PW), although named popup links are handled. * * @author nathanael */ public class FolioLinkUtils { //WW is a subset of PL, syntax the same. //<PL:Style,"url,file,exe"> //<WW:Style,"address"> //<QL:Style, query> //<JL:Jump,""> - added Jan 21,09. //<PX:Style,NamedPopupID> //<OL:Style,ObjectName,(opt)Class Name, Infobase Name, ZM (scale to fit) //<DL:Style,DataObjectName> //<ML:Style,"FolioMenuCommand"> //<UL:Style, user defined> //<EN:Style Name,"Query",Width,Height,"Title"> End note link //<link href="" infobase="" jumpdestination="" popupId="" program="" dataobject="" objectname="" scaleobject="" objectclass="" userdefined="" query="" recordsWithHits="" popupQuery="" public static boolean isOpeningLinkTag(FolioToken t) { return (!t.isClosing() && t.matches("^UL|QL|PL|OL|PX|WW|JL|EN|DL|ML$")); } public static boolean isClosingLinkTag(FolioToken t) { if (t.matches("EL")) return true; if (t.isClosing() && t.matches("^UL|QL|PL|OL|PX|WW|JL|EN|DL|ML$")) return true; return false; } public static SlxToken translate(FolioToken t) throws InvalidMarkupException { if (t.matches("UL")) { System.out.println("User link (unsupported Folio token):"); System.out.println(t.text); return FolioSlxTranslator.tryCommentOut(t, "UL"); //We comment out user links - we have no way to map them. } if (isClosingLinkTag(t)) return new SlxToken("</link>"); else if (isOpeningLinkTag(t)) { if (t.count() < 2 && !t.matches("UL")) throw new InvalidMarkupException("All links (PL, WW, QL, JL, PX, OL, DL, ML) except User Link (UL) must provide at least 2 arguments.", t); SlxToken st = new SlxToken("<link>").set("class", t.get(0)); //Class is always first. if (t.matches("WW|PL")) { //Web and program links. Program links are a superset of web links. Use Href for web addresses, program for exe's and documents. String cmd = t.get(1); if (t.matches("WW") || isUrl(cmd)) st.set("href", cmd.trim()); //Href for these. PL may use a local path or .exe.... If it's a valid URL, use href. else st.set("program", cmd); //TODO: path variable expansion "%%" = path to infobase. "%?" = path to folio views. if (t.count() > 2) throw new InvalidMarkupException("WW, PL, and DL links can only have 2 arguments.", t); } else if (t.matches("QL")) { //Query link st.set("query", t.get(1)); int ix = 2; while (ix < t.count()) { String opt = t.get(ix); if (opt.equalsIgnoreCase("RH")) st.set("showOnlyHitRecords", "true"); else if (st.get("infobase") == null) st.set("infobase", opt); else throw new InvalidMarkupException("Unrecognized option " + opt, t); ix++; } } else if (t.matches("OL")) { //Object link st.set("objectName", t.get(1)); int ix = 2; //May20,2011 - BUG: if Class name is blank, the infobase name will be used instead. //Added logic to use as infobase name if it contains '.nfo'. while (ix < t.count()) { String opt = t.get(ix); if (opt.equalsIgnoreCase("ZM")) st.set("zoomFit", "true"); else { if (st.get("infobase") == null && opt.toLowerCase().contains(".nfo")) st.set("infobase", opt); //If it has .nfo, the class name must have been omitted. else if (st.get("className") == null) st.set("className", opt); //Fill class name first else if (st.get("infobase") == null) st.set("infobase", opt); //Then infobase else throw new InvalidMarkupException("Unrecognized option " + opt, t); } ix++; } //Data link } else if (t.matches("DL")) { //Data link. References object definition. st.set("dataLink", t.get(1)); //Changed from objectName to dataLink Feb 2 , 2010. Conflicted with object links, and they don't have anything in common. if (t.count() > 2) throw new InvalidMarkupException("WW, PL, and DL links can only have 2 arguments.", t); //Menu link } else if (t.matches("ML")) { st.set("menu", t.get(1)); if (t.count() > 2) throw new InvalidMarkupException("ML (Menu links) can only have 2 arguments.", t); //Popup link } else if (t.matches("PX")) { st.set("popupTitle", t.get(1)); if (t.count() > 2) throw new InvalidMarkupException("PX (Named popup links) can only have 2 arguments.", t); //Jump link } else if (t.matches("JL")) { st.set("jumpDestination", "_" + t.get(1)); if (t.count() > 2) st.set("infobase", t.get(2)); if (t.count() > 3) throw new InvalidMarkupException("JL (Jump link) may only have 3 arguments." + t.text); //End note link (popup query link) } else if (t.matches("EN")) { st.set("query", t.get(1)); if (t.count() == 5) { st.set("popupWidth", t.get(2)); st.set("popupHeight", t.get(3)); st.set("title", t.get(4)); } else if (t.count() == 3) { st.set("title", t.get(2)); } else if (t.count() > 2) { throw new InvalidMarkupException("EN (End note link) may only have 2, 3, or 5 arguments" + t.text); } } else { throw new InvalidMarkupException("Link not supported: " + t.text); } return st; } return null; } /** * HAAACK. Not tested, not verified, not thought through. * * @param t * @param css * @return * @throws InvalidMarkupException */ public static String translateToFolio(SlxToken t, CssClassCleaner css) throws InvalidMarkupException { assert (t.matches("a|link")); /* Incoming tokens will be 'resolved', merged with their definitions. * I think this means that type="link" and style="?????" */ /* <link> (ghost tag 2/2) <link href="" infobase="" jumpdestination="" popupTitle="" program="" dataobject="" objectname="" scaleobject="" objectclass="" userdefined="" query="" recordsWithHits="" popupQuery="" /> Links cannot overlap or nest inside other links. Link tags can be either opening or closing, but </EL> is often used to close all types if links instead of the starting tag. Program link <PL:Style,"url,file,exe"> <link class="style" program="url, file, or exe"> Web link <WW:Style,"address"> <link class="style" href="address"> WW is a subset of PL, syntax the same. Object link <OL:Style,ObjectName,(opt)Class Name, Infobase Name, ZM (scale to fit) Data link <DL:Style,DataObjectName> Named popup link <PX:Style,NamedPopupID> Menu link <ML:Style,"FolioMenuCommand"> User Link <UL:Style, user defined> Jump link <JL:Style,"jump destination"> <link class="style" jumpdestination="jump destination"> Query link <QL:Style, query> <link class="style" query="query"> End note link EN ?? End link EL </link> */ String style = css.findOriginalName(t); if (t.isClosing()) return "<EL>"; if (!t.isOpening()) return null; if (t.get("program") != null) return "<PL:\"" + style + "\",\"" + t.get("program") + "\">"; if (t.get("href") != null) return "<WW:\"" + style + "\",\"" + t.get("href") + "\">"; if (t.get("dataLink") != null) return "<DL:\"" + style + "\",\"" + t.get("dataLink") + "\">"; if (t.get("popupTitle") != null) return "<PX:\"" + style + "\",\"" + t.get("popupTitle") + "\">"; if (t.get("menu") != null) return "<ML:\"" + style + "\",\"" + t.get("menu") + "\">"; if (t.get("jumpDestination") != null) { if (t.get("infobase") != null) return "<JL:\"" + style + "\",\"" + t.get("jumpdestination") + "\",\"" + t.get("infobase") + "\">"; else return "<JL:\"" + style + "\",\"" + t.get("jumpdestination") + "\">"; } /* else if (t.matches("QL")) { //Query link st.set("query", t.get(1)); int ix = 2; while(ix < t.count()){ String opt= t.get(ix); if (opt.equalsIgnoreCase("RH")) st.set("showOnlyHitRecords", "true"); else if (st.get("infobase") == null) st.set("infobase", opt); else throw new InvalidMarkupException("Unrecognized option " + opt,t); ix++; } }else if (t.matches("OL")){ //Object link st.set("objectName", t.get(1)); int ix = 2; while(ix < t.count()){ String opt= t.get(ix); if (opt.equalsIgnoreCase("ZM")) st.set("zoomFit", "true"); else if (st.get("className") == null) st.set("className", opt); //Fill class name first else if (st.get("infobase") == null) st.set("infobase", opt); //Then infobase else throw new InvalidMarkupException("Unrecognized option " + opt,t); ix++; } */ if (t.get("query") != null) { String s = "<QL:\"" + style + "\",\"" + t.get("query"); if ("true".equalsIgnoreCase(t.get("showOnlyHitRecords"))) s += ",RH"; if (t.get("infobase") != null) s += ",\"" + t.get("infobase") + "\""; return s + ">"; } if (t.get("objectName") != null) { String s = "<OL:\"" + style + "\",\"" + t.get("objectName"); if (t.get("className") != null) s += ",\"" + t.get("className") + "\""; if (t.get("infobase") != null) s += ",\"" + t.get("infobase") + "\""; if ("true".equalsIgnoreCase(t.get("zoomFit"))) s += ",ZM"; return s + ">"; } throw new InvalidMarkupException("Failed to translate link to folio: " + t.toTokenString()); } //SCHEME: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) protected static Pattern scheme = Pattern.compile("\\A\\s*(mailto:|[a-zA-Z][A-Za-z0-9+.-]*://)", Pattern.CASE_INSENSITIVE); protected static Pattern www = Pattern.compile("\\A\\s*www\\.([a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\\.)+[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](/|\\Z)"); private static boolean isUrl(String s) { String address = s.trim();//Trim whitespace ///www.something.something/ -> assume it's an (invalid) Url that can be fixed. //If it starts with scheme, assume it's a URL. Programs and program paths don't start with schemes. return www.matcher(address).find() || scheme.matcher(address).find(); } }