PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/XML/trunk/xml/hyperlinks/XMLHyperlinkSource.java

#
Java | 436 lines | 278 code | 28 blank | 130 comment | 70 complexity | 80f605ea88402e3a018c778c5013b7b5 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * XMLHyperlinkSource.java - Hyperlink source from the XML plugin
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2011 Eric Le Lay
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or any later version.
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. */
  21. package xml.hyperlinks;
  22. import java.io.Reader;
  23. import java.net.URI;
  24. import java.util.Arrays;
  25. import java.util.regex.Pattern;
  26. import java.util.regex.Matcher;
  27. import org.gjt.sp.jedit.View;
  28. import org.gjt.sp.jedit.jEdit;
  29. import org.gjt.sp.jedit.Buffer;
  30. import org.gjt.sp.util.Log;
  31. import sidekick.SideKickParsedData;
  32. import sidekick.IAsset;
  33. import sidekick.util.ElementUtil;
  34. import gatchan.jedit.hyperlinks.*;
  35. import static xml.Debug.*;
  36. import xml.Resolver;
  37. import xml.CharSequenceReader;
  38. import xml.XmlParsedData;
  39. import xml.parser.javacc.*;
  40. import xml.parser.XmlTag;
  41. import xml.completion.ElementDecl;
  42. import xml.completion.IDDecl;
  43. /**
  44. * Provides hyperlinks from XML attributes.
  45. * Supported hyperlinks are :
  46. * <ul>
  47. * <li>XInclude (&lt;xi:include href="..."/&gt;). Fragments are not supported:
  48. * the buffer is opened and that's all.
  49. * </li>
  50. * <li>simple XLinks (&lt;myelt xlink:href="..."/&gt;). Only simple links are supported:
  51. * the buffer is opened and that's all.
  52. * </li>
  53. * <li>IDREF attributes, when the buffer has been parsed and the attribute has the
  54. * IDREF datatype. This includes docbook 4.x links (&lt;xref linkend="id"/&gt;).
  55. * </li>
  56. * <li>XML Schema (XSD) schema location (&lt;myelt xsi:schemaLocation="ns1 url1..."&gt;)
  57. * and no namespace schema location (&lt;myelt xsi:noNamespaceSchemaLocation="url"&gt;).
  58. * </li>
  59. * <li>attributes with datatype anyURI
  60. * eg. XSD include and import (&lt;xs:include schemaLocation="url"/&gt;).
  61. * </li>
  62. * <li>DocBook ulink (&lt;ulink url="..."&gt;)</li>
  63. * </ul>
  64. *
  65. * @todo HTML attributes
  66. * @todo HTML BASE tag (beware: it's in HTML/HEAD)
  67. *
  68. * @author Eric Le Lay
  69. * @version $Id: XMLHyperlinkSource.java 21198 2012-02-24 11:19:21Z kerik-sf $
  70. */
  71. public class XMLHyperlinkSource implements HyperlinkSource
  72. {
  73. /**
  74. * Returns the hyperlink for the given offset.
  75. * returns an hyperlink as soon as pointer enters the attribute's value
  76. *
  77. * @param buffer the buffer
  78. * @param offset the offset
  79. * @return the hyperlink (or null if there is no hyperlink)
  80. */
  81. public Hyperlink getHyperlink(Buffer buffer, int offset){
  82. View view = jEdit.getActiveView();
  83. XmlParsedData data = XmlParsedData.getParsedData(view, false);
  84. if(data==null)return null;
  85. IAsset asset = data.getAssetAtOffset(offset);
  86. if(asset == null){
  87. Log.log(Log.DEBUG, XMLHyperlinkSource.class,"no Sidekick asset here");
  88. return null;
  89. } else {
  90. int wantedLine = buffer.getLineOfOffset(offset);
  91. int wantedLineOffset = buffer.getVirtualWidth(wantedLine, offset - buffer.getLineStartOffset(wantedLine));
  92. /* use the XML javacc parser to parse the start tag.*/
  93. int max = buffer.getLength();
  94. int sA = asset.getStart().getOffset();
  95. int lA = asset.getEnd().getOffset()-sA;
  96. if(sA < 0 || sA > max){
  97. return null;
  98. }
  99. if(lA < 0 || sA+lA > max){
  100. return null;
  101. }
  102. CharSequence toParse = buffer.getSegment(sA,lA);
  103. int line = buffer.getLineOfOffset(sA);
  104. int col = buffer.getVirtualWidth(line, sA-buffer.getLineStartOffset(line)+1);
  105. Reader r = new CharSequenceReader(toParse);
  106. XmlParser parser = new XmlParser(r, line+1, col);
  107. parser.setTabSize( buffer.getTabSize() );
  108. try{
  109. XmlDocument.XmlElement startTag = parser.Tag();
  110. int start = ElementUtil.createStartPosition(buffer,startTag).getOffset();
  111. int end= ElementUtil.createEndPosition(buffer,startTag).getOffset();
  112. /* if the offset is inside start tag */
  113. if(offset <= end)
  114. {
  115. XmlDocument.AttributeList al = ((XmlDocument.Tag)startTag).attributeList;
  116. if(al == null){
  117. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"no attribute in this element");
  118. return null;
  119. }else{
  120. for(XmlDocument.Attribute att: al.attributes){
  121. // offset is inside attribute's value
  122. if( ( att.getValueStartLocation().line < wantedLine+1
  123. || (att.getValueStartLocation().line == wantedLine+1
  124. && att.getValueStartLocation().column <= wantedLineOffset)
  125. )
  126. &&
  127. ( att.getEndLocation().line > wantedLine+1
  128. || (att.getEndLocation().line == wantedLine+1
  129. && att.getEndLocation().column > wantedLineOffset)
  130. )
  131. )
  132. {
  133. if(asset instanceof XmlTag) {
  134. return getHyperlinkForAttribute(buffer, offset, data, asset, att);
  135. }
  136. }
  137. }
  138. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"not inside attributes");
  139. return null;
  140. }
  141. }else{
  142. return null;
  143. }
  144. }catch(ParseException pe){
  145. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG, XMLHyperlinkSource.class, "error parsing element", pe);
  146. return null;
  147. }
  148. }
  149. }
  150. /**
  151. * get an hyperlink for an identified XML attribute
  152. * @param buffer current buffer
  153. * @param offset offset where an hyperlink is required in current buffer
  154. * @param data sidekick tree for current buffer
  155. * @param asset element containing offset
  156. * @param att parsed attribute
  157. */
  158. public Hyperlink getHyperlinkForAttribute(
  159. Buffer buffer, int offset, XmlParsedData data,
  160. IAsset asset, XmlDocument.Attribute att)
  161. {
  162. XmlTag sideKickTag = (XmlTag)asset;
  163. if(sideKickTag.attributes == null){
  164. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"Sidekick version of this element doesn't have attributes");
  165. return null;
  166. }
  167. int attIndex = sideKickTag.attributes.getIndex(att.name);
  168. if(attIndex < 0){
  169. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"Sidekick version of this element doesn't have this attribute: "+att.name);
  170. return null;
  171. }
  172. String tagNS = sideKickTag.namespace;
  173. String tagLocalName = sideKickTag.getLocalName();
  174. String ns = sideKickTag.attributes.getURI(attIndex);
  175. String localName = att.name.contains(":")? sideKickTag.attributes.getLocalName(attIndex) : att.name;
  176. String value = sideKickTag.attributes.getValue(attIndex);
  177. Hyperlink h = getHyperlinkForAttribute(buffer, offset,
  178. tagNS,tagLocalName, ns, localName, value,
  179. data, sideKickTag, att);
  180. if(h == null) {
  181. ElementDecl eltDecl = data.getElementDecl(sideKickTag.getName(),offset);
  182. if(eltDecl == null){
  183. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"no element declaration for "+tagLocalName);
  184. }else{
  185. ElementDecl.AttributeDecl attDecl = eltDecl.attributeHash.get(localName);
  186. if(attDecl == null){
  187. if(DEBUG_HYPERLINKS)Log.log(Log.DEBUG,XMLHyperlinkSource.class,"no attribute declaration for "+localName);
  188. return null;
  189. }else{
  190. if("IDREF".equals(attDecl.type)){
  191. return getHyperlinkForIDREF(buffer, data, value, att);
  192. }else if("anyURI".equals(attDecl.type)){
  193. // FIXME: shall it use xml:base ?
  194. String href = resolve(value, buffer, offset, data, sideKickTag, false);
  195. if(href!=null){
  196. return newJEditOpenFileHyperlink(buffer, att, href);
  197. }
  198. }else if("IDREFS".equals(attDecl.type)){
  199. return getHyperlinkForIDREFS(buffer, offset, data, value, att);
  200. }
  201. return null;
  202. }
  203. }
  204. } else {
  205. return h;
  206. }
  207. return null;
  208. }
  209. /**
  210. * creates an hyperlink to the location of the element with id (in same or another buffer).
  211. * @param buffer current buffer
  212. * @param data sidekick tree
  213. * @param id id we are looking for
  214. * @param att parsed attribute (for hyperlink boundaries)
  215. */
  216. public Hyperlink getHyperlinkForIDREF(Buffer buffer,
  217. XmlParsedData data, String id, XmlDocument.Attribute att)
  218. {
  219. IDDecl idDecl = data.getIDDecl(id);
  220. if(idDecl == null){
  221. return null;
  222. } else{
  223. return newJEditOpenFileAndGotoHyperlink(buffer, att,
  224. idDecl.uri, idDecl.line, idDecl.column);
  225. }
  226. }
  227. // {{{ getHyperlinkForIDREFS() method
  228. private static final Pattern noWSPattern = Pattern.compile("[^\\s]+");
  229. /**
  230. * creates an hyperlink to the location of the element with id (in same or another buffer)
  231. * @param buffer current buffer
  232. * @param offset offset of required hyperlink
  233. * @param data sidekick tree
  234. * @param attValue ids in the attribute
  235. * @param att parsed attribute (for hyperlink boundaries)
  236. */
  237. public Hyperlink getHyperlinkForIDREFS(Buffer buffer, int offset,
  238. XmlParsedData data, String attValue, XmlDocument.Attribute att)
  239. {
  240. int attStart = xml.ElementUtil.createOffset(buffer, att.getValueStartLocation());
  241. // +1 for the quote around the attribute value
  242. attStart++;
  243. Matcher m = noWSPattern.matcher(attValue);
  244. while(m.find()){
  245. int st = m.start(0);
  246. int nd = m.end(0);
  247. if(attStart + st <= offset && attStart + nd >= offset){
  248. IDDecl idDecl = data.getIDDecl(m.group(0));
  249. if(idDecl==null)return null;
  250. int start = attStart + st;
  251. int end= attStart + nd;
  252. int line = buffer.getLineOfOffset(start);
  253. return new jEditOpenFileAndGotoHyperlink(start, end, line, idDecl.uri, idDecl.line, idDecl.column);
  254. }
  255. }
  256. return null;
  257. }//}}}
  258. //{{{ getHyperlinkForAttribute(tagNS,tagName,attNS,attName) method
  259. private static final Pattern nsURIPairsPattern = Pattern.compile("[^\\s]+\\s+([^\\s]+)");
  260. /**
  261. * recognize hyperlink attributes by their parent element's
  262. * namespace:localname and/or their namespace:localname
  263. */
  264. public Hyperlink getHyperlinkForAttribute(Buffer buffer, int offset,
  265. String tagNS, String tagLocalName,
  266. String attNS, String attLocalName, String attValue,
  267. XmlParsedData data, XmlTag tag, XmlDocument.Attribute att)
  268. {
  269. String href = null;
  270. if("http://www.w3.org/2001/XInclude".equals(tagNS)
  271. && "include".equals(tagLocalName)
  272. && "href".equals(attLocalName))
  273. {
  274. href = resolve(attValue, buffer, offset, data, tag, true);
  275. } else if("http://www.w3.org/1999/xlink".equals(attNS)
  276. && "href".equals(attLocalName))
  277. {
  278. href = resolve(attValue, buffer, offset, data, tag, true);
  279. } else if("http://www.w3.org/2001/XMLSchema-instance".equals(attNS)
  280. && "noNamespaceSchemaLocation".equals(attLocalName))
  281. {
  282. href = resolve(attValue, buffer, offset, data, tag, false);
  283. } else if("".equals(tagNS) && "ulink".equals(tagLocalName)
  284. && "url".equals(attLocalName))
  285. {
  286. href = resolve(attValue, buffer, offset, data, tag, false);
  287. } else if("http://www.w3.org/2001/XMLSchema-instance".equals(attNS)
  288. && "schemaLocation".equals(attLocalName))
  289. {
  290. // +1 for the quote around the attribute value
  291. int attStart = xml.ElementUtil.createOffset(buffer, att.getValueStartLocation()) +1;
  292. Matcher m = nsURIPairsPattern.matcher(attValue);
  293. // find will accept unbalanced pairs of ns->uri
  294. while(m.find()){
  295. int st = m.start(1);
  296. int nd = m.end(1);
  297. if(attStart + st <= offset && attStart + nd >= offset){
  298. href = resolve(m.group(1), buffer, offset, data, tag, false);
  299. if(href==null)href=m.group(1);
  300. int start = attStart + st;
  301. int end= attStart + nd;
  302. int line = buffer.getLineOfOffset(start);
  303. return new jEditOpenFileHyperlink(start, end, line, href);
  304. }
  305. }
  306. }
  307. if(href==null){
  308. return null;
  309. }else{
  310. return newJEditOpenFileHyperlink(
  311. buffer, att, href);
  312. }
  313. }//}}}
  314. /**
  315. * resolve a potentially relative uri using xml:base attributes,
  316. * the buffer's URL, xml.Resolver.
  317. * Has the effect of opening the cached document if it's in cache (eg. docbook XSD if not in catalog).
  318. * Maybe this is not desirable, because if there is a relative link in this document, it won't work
  319. * because the document will be .jedit/dtds/cachexxxxx.xml and not the real url.
  320. *
  321. * @param uri text of uri to reach
  322. * @param buffer current buffer
  323. * @param offset offset in current buffer where an hyperlink is required
  324. * @param data SideKick parsed data
  325. * @param tag SideKick asset
  326. * @param useXmlBase should xml:base attribute be used to resolve uri (only for XML!)
  327. *
  328. * @return resolved URL
  329. */
  330. public String resolve(String uri, Buffer buffer, int offset, XmlParsedData data, XmlTag tag,
  331. boolean useXmlBase)
  332. {
  333. String href = null;
  334. String base = xml.PathUtilities.pathToURL(buffer.getPath());
  335. if(useXmlBase){
  336. try {
  337. URI baseURI = URI.create(base);
  338. Object[] pathObjs = data.getObjectsTo(offset);
  339. // go down the tree, resolving existing xml:base uri if they exist
  340. for(int i=1; i<pathObjs.length;i++){ //first object (i==0) is a SourceAsset for the file
  341. XmlTag t = (XmlTag)pathObjs[i];
  342. String newBase = t.attributes.getValue("xml:base"); // xml is a reserved prefix
  343. if(newBase!=null){
  344. baseURI = baseURI.resolve(newBase);
  345. }
  346. }
  347. if(!base.equals(baseURI.toString())){
  348. // add a dummy component, otherwise xml.Resolver
  349. // removes the last part of the xml:base
  350. // FIXME: review xml.Resolver : it should only be used with URLs
  351. // for current, now, so could use URI.resolve() instead of removing
  352. // last part of the path to get the parent...
  353. baseURI = baseURI.resolve("dummy");
  354. base = baseURI.toString();
  355. }
  356. }catch(IllegalArgumentException e){
  357. Log.log(Log.WARNING, XMLHyperlinkSource.class, "error resolving uri", e);
  358. }
  359. }
  360. try{
  361. href = Resolver.instance().resolveEntityToPath(
  362. "", /*name*/
  363. "", /*publicId*/
  364. base, /*current, augmented by xml:base */
  365. uri);
  366. }catch(java.io.IOException ioe){
  367. Log.log(Log.ERROR,XMLHyperlinkSource.class,"error resolving href="+uri,ioe);
  368. }
  369. return href;
  370. }
  371. /**
  372. * create an hyperlink for attribute att.
  373. * the hyperlink will span whole attribute value
  374. * @param buffer current buffer
  375. * @param att parsed attribute
  376. * @param href uri to open
  377. */
  378. public Hyperlink newJEditOpenFileHyperlink(
  379. Buffer buffer, XmlDocument.Attribute att, String href)
  380. {
  381. int start = xml.ElementUtil.createOffset(buffer,att.getValueStartLocation());
  382. int end= xml.ElementUtil.createOffset(buffer,att.getEndLocation());
  383. int line = buffer.getLineOfOffset(start);
  384. return new jEditOpenFileHyperlink(start, end, line, href);
  385. }
  386. /**
  387. * create an hyperlink for attribute att.
  388. * the hyperlink will span whole attribute value
  389. * @param buffer current buffer
  390. * @param att parsed attribute
  391. * @param href uri to open
  392. * @param gotoLine target line in buffer
  393. * @param gotoCol target column in buffer
  394. */
  395. public Hyperlink newJEditOpenFileAndGotoHyperlink(
  396. Buffer buffer, XmlDocument.Attribute att, String href, int gotoLine, int gotoCol)
  397. {
  398. int start = xml.ElementUtil.createOffset(buffer,att.getValueStartLocation());
  399. int end= xml.ElementUtil.createOffset(buffer,att.getEndLocation());
  400. int line = buffer.getLineOfOffset(start);
  401. return new jEditOpenFileAndGotoHyperlink(start, end, line, href, gotoLine, gotoCol);
  402. }
  403. public static HyperlinkSource create(){
  404. return new FallbackHyperlinkSource(
  405. Arrays.asList(new XMLHyperlinkSource(),
  406. new gatchan.jedit.hyperlinks.url.URLHyperlinkSource()));
  407. }
  408. }