PageRenderTime 354ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/XML/tags/release-2-8-1/xml/hyperlinks/XMLHyperlinkSource.java

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