##### Coverage Report - org.apache.any23.extractor.rdfa.RDFa11Parser

Classes in this File | Line Coverage | Branch Coverage | Complexity
RDFa11Parser |
0% | 0/333
0% | 0/240
| 4.313725490196078;4.314
RDFa11Parser$1 |
0% | 0/1
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$EvaluationContext |
0% | 0/11
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$IncompleteTriple |
0% | 0/17
0% | 0/15
| 4.313725490196078;4.314
RDFa11Parser$IncompleteTripleDirection |
0% | 0/3
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$PrefixMap |
0% | 0/4
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$ResolutionPolicy |
0% | 0/4
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$URIMapping |
0% | 0/4
N/A | N/A
| 4.313725490196078;4.314
RDFa11Parser$Vocabulary |
0% | 0/4
N/A | N/A
| 4.313725490196078;4.314

1 |  |
     /*
2 |  |
      * Licensed to the Apache Software Foundation (ASF) under one or more
3 |  |
      * contributor license agreements.  See the NOTICE file distributed with
4 |  |
      * this work for additional information regarding copyright ownership.
5 |  |
      * The ASF licenses this file to You under the Apache License, Version 2.0
6 |  |
      * (the "License"); you may not use this file except in compliance with
7 |  |
      * the License.  You may obtain a copy of the License at
8 |  |
      *
9 |  |
      *  http://www.apache.org/licenses/LICENSE-2.0
10 |  |
      *
11 |  |
      * Unless required by applicable law or agreed to in writing, software
12 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
13 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  |
      * See the License for the specific language governing permissions and
15 |  |
      * limitations under the License.
16 |  |
      */
17 |  |
18 |  |
     package org.apache.any23.extractor.rdfa;
19 |  |
20 |  |
     import org.apache.any23.extractor.ErrorReporter;
21 |  |
     import org.apache.any23.extractor.ExtractionResult;
22 |  |
     import org.apache.any23.extractor.html.DomUtils;
23 |  |
     import org.apache.any23.rdf.RDFUtils;
24 |  |
     import org.openrdf.model.Literal;
25 |  |
     import org.openrdf.model.Resource;
26 |  |
     import org.openrdf.model.URI;
27 |  |
     import org.openrdf.model.Value;
28 |  |
     import org.openrdf.model.vocabulary.RDF;
29 |  |
     import org.slf4j.Logger;
30 |  |
     import org.slf4j.LoggerFactory;
31 |  |
     import org.w3c.dom.Document;
32 |  |
     import org.w3c.dom.NamedNodeMap;
33 |  |
     import org.w3c.dom.Node;
34 |  |
     import org.w3c.dom.NodeList;
35 |  |
36 |  |
     import javax.xml.transform.TransformerException;
37 |  |
     import java.io.IOException;
38 |  |
     import java.net.MalformedURLException;
39 |  |
     import java.net.URISyntaxException;
40 |  |
     import java.net.URL;
41 |  |
     import java.util.ArrayList;
42 |  |
     import java.util.HashMap;
43 |  |
     import java.util.List;
44 |  |
     import java.util.Map;
45 |  |
     import java.util.Stack;
46 |  |
47 |  |
     /**
48 |  |
      * This parser is able to extract <a href="http://www.w3.org/TR/rdfa-syntax/">RDFa 1.0</a> and
49 |  |
      * <a href="http://www.w3.org/TR/rdfa-core/">RDFa 1.1</a> statements from any <i>(X)HTML</i> document.
50 |  |
      *
51 |  |
      * @author Michele Mostarda (mostarda@fbk.eu)
52 |  |
      */
53 | 0 |
     public class RDFa11Parser {
54 |  |
55 | 0 |
         private static final Logger logger = LoggerFactory.getLogger(RDFa11Parser.class);
56 |  |
57 |  |
         public static final String CURIE_SEPARATOR      = ":";
58 |  |
         public static final char   URI_PREFIX_SEPARATOR = ':';
59 |  |
         public static final String URI_SCHEMA_SEPARATOR = "://";
60 |  |
         public static final String URI_PATH_SEPARATOR   = "/";
61 |  |
62 |  |
         public static final String HEAD_TAG = "HEAD";
63 |  |
         public static final String BODY_TAG = "BODY";
64 |  |
65 |  |
         public static final String XMLNS_ATTRIBUTE    = "xmlns";
66 |  |
         public static final String XML_LANG_ATTRIBUTE = "xml:lang";
67 |  |
68 |  |
         public static final String REL_ATTRIBUTE      = "rel";
69 |  |
         public static final String REV_ATTRIBUTE      = "rev";
70 |  |
71 |  |
         public static final String ABOUT_ATTRIBUTE    = "about";
72 |  |
         public static final String RESOURCE_ATTRIBUTE = "resource";
73 |  |
         public static final String SRC_ATTRIBUTE      = "src";
74 |  |
         public static final String HREF_ATTRIBUTE     = "href";
75 |  |
76 | 0 |
         public static final String[] SUBJECT_ATTRIBUTES = {
77 |  |
                 ABOUT_ATTRIBUTE,
78 |  |
                 SRC_ATTRIBUTE,
79 |  |
                 RESOURCE_ATTRIBUTE,
80 |  |
                 HREF_ATTRIBUTE
81 |  |
         };
82 |  |
83 |  |
         public static final String PREFIX_ATTRIBUTE   = "prefix";
84 |  |
         public static final String TYPEOF_ATTRIBUTE   = "typeof";
85 |  |
         public static final String PROPERTY_ATTRIBUTE = "property";
86 |  |
         public static final String DATATYPE_ATTRIBUTE = "datatype";
87 |  |
         public static final String CONTENT_ATTRIBUTE  = "content";
88 |  |
         public static final String VOCAB_ATTRIBUTE    = "vocab";
89 |  |
         // TODO: introduce support for RDFa profiles. (http://www.w3.org/TR/rdfa-core/#s_profiles)
90 |  |
         public static final String PROFILE_ATTRIBUTE  = "profile";
91 |  |
92 |  |
         public static final String XML_LITERAL_DATATYPE = "rdf:XMLLiteral";
93 |  |
94 |  |
         public static final String XMLNS_DEFAULT = "http://www.w3.org/1999/xhtml";
95 |  |
96 |  |
         private ErrorReporter errorReporter;
97 |  |
98 |  |
         private URL documentBase;
99 |  |
100 | 0 |
         private final Stack<URIMapping> uriMappingStack = new Stack<URIMapping>();
101 |  |
102 | 0 |
         private final Stack<Vocabulary> vocabularyStack = new Stack<Vocabulary>();
103 |  |
104 | 0 |
         private final List<IncompleteTriple> listOfIncompleteTriples = new ArrayList<IncompleteTriple>();
105 |  |
106 | 0 |
         private final Stack<EvaluationContext> evaluationContextStack = new Stack<EvaluationContext>();
107 |  |
108 |  |
         protected static URL getDocumentBase(URL documentURL, Document document) throws MalformedURLException {
109 |  |
             String base;
110 | 0 |
             base = DomUtils.find(document, "/HTML/HEAD/BASE/@href");                  // Non XHTML documents.
111 | 0 |
             if( ! "".equals(base) ) return new URL(base);
112 | 0 |
             base = DomUtils.find(document, "//*/h:head/h:base[position()=1]/@href");  // XHTML documents.
113 | 0 |
             if( ! "".equals(base) ) return new URL(base);
114 | 0 |
             return documentURL;
115 |  |
         }
116 |  |
117 |  |
         /**
118 |  |
          * Given a prefix declaration returns a list of <code>prefixID:prefixURL</code> strings
119 |  |
          * normalizing blanks where present.
120 |  |
          *
121 |  |
          * @param prefixesDeclaration
122 |  |
          * @return
123 |  |
          */
124 |  |
         protected static String[] extractPrefixSections(String prefixesDeclaration) {
125 | 0 |
             final String[] parts = prefixesDeclaration.split("\\s");
126 | 0 |
             final List<String> out = new ArrayList<String>();
127 | 0 |
             int i = 0;
128 | 0 |
             while(i < parts.length) {
129 | 0 |
                 final String part = parts[i];
130 | 0 |
                 if(part.length() == 0) {
131 | 0 |
                     i++;
132 | 0 |
                     continue;
133 |  |
                 }
134 | 0 |
                 if(part.charAt( part.length() -1 ) == URI_PREFIX_SEPARATOR) {
135 | 0 |
                     i++;
136 | 0 |
                     while(i < parts.length && parts[i].length() == 0) i++;
137 | 0 |
                     out.add( part + (i < parts.length ? parts[i] : "") );
138 | 0 |
                     i++;
139 |  |
                 } else {
140 | 0 |
                     out.add(parts[i]);
141 | 0 |
                     i++;
142 |  |
                 }
143 | 0 |
             }
144 | 0 |
             return out.toArray( new String[out.size()] );
145 |  |
         }
146 |  |
147 |  |
         protected static boolean isAbsoluteURI(String uri) {
148 | 0 |
             return uri.contains(URI_SCHEMA_SEPARATOR);
149 |  |
         }
150 |  |
151 |  |
         protected static boolean isCURIE(String curie) {
152 | 0 |
             if(curie == null) {
153 | 0 |
                 throw new NullPointerException("curie string cannot be null.");
154 |  |
             }
155 | 0 |
             if(curie.trim().length() == 0) return false;
156 |  |
157 |  |
             // '[' PREFIX ':' VALUE ']'
158 | 0 |
             if( curie.charAt(0) != '[' || curie.charAt(curie.length() -1) != ']') return false;
159 | 0 |
             int separatorIndex = curie.indexOf(CURIE_SEPARATOR);
160 | 0 |
             return separatorIndex > 0 && curie.indexOf(CURIE_SEPARATOR, separatorIndex + 1) == -1;
161 |  |
         }
162 |  |
163 |  |
         protected static boolean isCURIEBNode(String curie) {
164 | 0 |
             return isCURIE(curie) && curie.substring(1, curie.length() -1).split(CURIE_SEPARATOR)[0].equals("_");
165 |  |
         }
166 |  |
167 |  |
         protected static boolean isRelativeNode(Node node) {
168 | 0 |
             return DomUtils.hasAttribute(node, REL_ATTRIBUTE) || DomUtils.hasAttribute(node, REV_ATTRIBUTE);
169 |  |
         }
170 |  |
171 |  |
         // RDFa1.0[5.5.9.2]
172 |  |
         protected static Literal getAsPlainLiteral(Node node, String currentLanguage) {
173 | 0 |
             final String content = DomUtils.readAttribute(node, CONTENT_ATTRIBUTE, null);
174 | 0 |
             if(content != null) return RDFUtils.literal(content, currentLanguage);
175 |  |
176 | 0 |
             if(! node.hasChildNodes() ) return RDFUtils.literal("", currentLanguage);
177 |  |
178 | 0 |
             final String nodeTextContent = node.getTextContent();
179 | 0 |
             return nodeTextContent == null ? null : RDFUtils.literal(nodeTextContent.trim(), currentLanguage);
180 |  |
         }
181 |  |
182 |  |
         protected static Literal getAsXMLLiteral(Node node) throws IOException, TransformerException {
183 | 0 |
             final String datatype = DomUtils.readAttribute(node, DATATYPE_ATTRIBUTE, null);
184 | 0 |
             if(! XML_LITERAL_DATATYPE.equals(datatype)) return null;
185 |  |
186 | 0 |
             final String xmlSerializedNode = DomUtils.serializeToXML(node, false);
187 | 0 |
             return RDFUtils.literal(xmlSerializedNode, RDF.XMLLITERAL);
188 |  |
         }
189 |  |
190 |  |
         protected static boolean isXMLNSDeclared(Document document) {
191 | 0 |
             final String attributeValue = document.getDocumentElement().getAttribute(XMLNS_ATTRIBUTE);
192 | 0 |
             if(attributeValue.length() == 0) return false;
193 | 0 |
             return XMLNS_DEFAULT.equals(attributeValue);
194 |  |
         }
195 |  |
196 | 0 |
         public RDFa11Parser() {}
197 |  |
198 |  |
         /**
199 |  |
          * <a href="http://www.w3.org/TR/rdfa-syntax/#s_model">RDFa Syntax - Processing Model</a>.
200 |  |
          *
201 |  |
          * @param documentURL
202 |  |
          * @param extractionResult
203 |  |
          * @param document
204 |  |
          */
205 |  |
         public void processDocument(URL documentURL, Document document, ExtractionResult extractionResult)
206 |  |
         throws RDFa11ParserException {
207 |  |
             try {
208 | 0 |
                 this.errorReporter = extractionResult;
209 |  |
210 |  |
                 // Check RDFa1.0[4.1.3] : default XMLNS declaration.
211 | 0 |
                 if( ! isXMLNSDeclared(document)) {
212 | 0 |
                     reportError(
213 |  |
                             document.getDocumentElement(),
214 |  |
                             String.format(
215 |  |
                                     "The default %s namespace is expected to be declared and equal to '%s' .",
216 |  |
                                     XMLNS_ATTRIBUTE, XMLNS_DEFAULT
217 |  |
                             )
218 |  |
                     );
219 |  |
                 }
220 |  |
221 |  |
                 try {
222 | 0 |
                     documentBase = getDocumentBase(documentURL, document);
223 | 0 |
                 } catch (MalformedURLException murle) {
224 | 0 |
                     throw new RDFa11ParserException("Invalid document base URL.", murle);
225 | 0 |
                 }
226 |  |
227 |  |
                 // RDFa1.0[5.5.1]
228 | 0 |
                 pushContext(document, new EvaluationContext(documentBase));
229 |  |
230 | 0 |
                 depthFirstNode(document, extractionResult);
231 |  |
232 |  |
                 assert listOfIncompleteTriples.isEmpty()
233 |  |
                         :
234 | 0 |
                        "The list of incomplete triples is expected to be empty at the end of processing.";
235 |  |
             } finally {
236 | 0 |
                 reset();
237 | 0 |
             }
238 | 0 |
         }
239 |  |
240 |  |
         /**
241 |  |
          * Resets the parser to the original state.
242 |  |
          */
243 |  |
         public void reset() {
244 | 0 |
             errorReporter = null;
245 | 0 |
             documentBase  = null;
246 | 0 |
             uriMappingStack.clear();
247 | 0 |
             listOfIncompleteTriples.clear();
248 | 0 |
             evaluationContextStack.clear();
249 | 0 |
         }
250 |  |
251 |  |
         /**
252 |  |
          * Updates the vocabulary context with possible <em>@vocab</em> declarations.
253 |  |
          *
254 |  |
          * @param currentNode the current node.
255 |  |
          */
256 |  |
         protected void updateVocabulary(Node currentNode) {
257 | 0 |
             final String vocabularyStr = DomUtils.readAttribute(currentNode, VOCAB_ATTRIBUTE, null);
258 | 0 |
             if(vocabularyStr == null) return;
259 |  |
             try {
260 | 0 |
                 pushVocabulary(currentNode, RDFUtils.uri(vocabularyStr));
261 | 0 |
             } catch (Exception e) {
262 | 0 |
                 reportError(currentNode, String.format("Invalid vocabulary [%s], must be a URI.", vocabularyStr));
263 | 0 |
             }
264 | 0 |
         }
265 |  |
266 |  |
         /**
267 |  |
          * Updates the URI mapping with the XMLNS attributes declared in the current node.
268 |  |
          *
269 |  |
          * @param node input node.
270 |  |
          */
271 |  |
         protected void updateURIMapping(Node node) {
272 | 0 |
             final NamedNodeMap attributes = node.getAttributes();
273 | 0 |
             if (null == attributes) return;
274 |  |
275 |  |
             Node attribute;
276 | 0 |
             final List<PrefixMap> prefixMapList = new ArrayList<PrefixMap>();
277 | 0 |
             final String namespacePrefix = XMLNS_ATTRIBUTE + URI_PREFIX_SEPARATOR;
278 | 0 |
             for (int a = 0; a < attributes.getLength(); a++) {
279 | 0 |
                 attribute = attributes.item(a);
280 | 0 |
                 if (attribute.getNodeName().startsWith(namespacePrefix)) {
281 | 0 |
                     prefixMapList.add(
282 |  |
                             new PrefixMap(
283 |  |
                                 attribute.getNodeName().substring(namespacePrefix.length()),
284 |  |
                                 resolveURI(attribute.getNodeValue())
285 |  |
                             )
286 |  |
                     );
287 |  |
                 }
288 |  |
             }
289 |  |
290 | 0 |
             extractPrefixes(node, prefixMapList);
291 |  |
292 | 0 |
             if(prefixMapList.size() == 0) return;
293 | 0 |
             pushMappings(
294 |  |
                     node,
295 |  |
                     prefixMapList
296 |  |
             );
297 | 0 |
         }
298 |  |
299 |  |
         /**
300 |  |
          * Returns a URI mapping for a given prefix.
301 |  |
          *
302 |  |
          * @param prefix input prefix.
303 |  |
          * @return URI mapping.
304 |  |
          */
305 |  |
         protected URI getMapping(String prefix) {
306 | 0 |
             for (URIMapping uriMapping : uriMappingStack) {
307 | 0 |
                 final URI mapping = uriMapping.map.get(prefix);
308 | 0 |
                 if (mapping != null) {
309 | 0 |
                     return mapping;
310 |  |
                 }
311 | 0 |
             }
312 | 0 |
             return null;
313 |  |
         }
314 |  |
315 |  |
         /**
316 |  |
          * Resolves a <rm>whitelist</em> separated list of <i>CURIE</i> or <i>URI</i>.
317 |  |
          *
318 |  |
          * @param n current node.
319 |  |
          * @param curieOrURIList list of CURIE/URI.
320 |  |
          * @return
321 |  |
          * @throws URISyntaxException
322 |  |
          */
323 |  |
         protected URI[] resolveCurieOrURIList(Node n, String curieOrURIList, boolean termAllowed)
324 |  |
         throws URISyntaxException {
325 | 0 |
             if(curieOrURIList == null || curieOrURIList.trim().length() == 0) return new URI[0];
326 |  |
327 | 0 |
             final String[] curieOrURIListParts = curieOrURIList.split("\\s");
328 | 0 |
             final List<URI> result = new ArrayList<URI>();
329 |  |
             Resource curieOrURI;
330 | 0 |
             for(String curieORURIListPart : curieOrURIListParts) {
331 | 0 |
                 curieOrURI = resolveCURIEOrURI(curieORURIListPart, termAllowed);
332 | 0 |
                 if(curieOrURI != null && curieOrURI instanceof URI) {
333 | 0 |
                     result.add((URI) curieOrURI);
334 |  |
                 } else {
335 | 0 |
                     reportError(n, String.format("Invalid CURIE '%s' : expected URI, found BNode.", curieORURIListPart));
336 |  |
                 }
337 |  |
             }
338 | 0 |
             return result.toArray(new URI[result.size()]);
339 |  |
         }
340 |  |
341 |  |
         /**
342 |  |
          * Resolves a URI string as URI.
343 |  |
          *
344 |  |
          * @param uriStr
345 |  |
          * @return
346 |  |
          */
347 |  |
         protected URI resolveURI(String uriStr) {
348 | 0 |
             return RDFUtils.uri(uriStr);
349 |  |
         }
350 |  |
351 |  |
         /**
352 |  |
          * Resolves a <i>CURIE</i> or <i>URI</i> string.
353 |  |
          *
354 |  |
          * @param curieOrURI
355 |  |
          * @param termAllowed if <code>true</code> the resolution can be a term.
356 |  |
          * @return the resolved resource.
357 |  |
          */
358 |  |
         protected Resource resolveCURIEOrURI(String curieOrURI, boolean termAllowed) {
359 | 0 |
             if( isCURIE(curieOrURI) ) {
360 | 0 |
                 return resolveNamespacedURI(curieOrURI.substring(1, curieOrURI.length() - 1), ResolutionPolicy.NSRequired);
361 |  |
             }
362 | 0 |
             if(isAbsoluteURI(curieOrURI)) return resolveURI(curieOrURI);
363 | 0 |
             return resolveNamespacedURI(
364 |  |
                     curieOrURI,
365 |  |
                     termAllowed ? ResolutionPolicy.TermAllowed : ResolutionPolicy.NSNotRequired
366 |  |
             );
367 |  |
         }
368 |  |
369 |  |
         /**
370 |  |
          * Pushes a context whiting the evaluation context stack, associated to tha given generation node.
371 |  |
          *
372 |  |
          * @param current
373 |  |
          * @param ec
374 |  |
          */
375 |  |
         private void pushContext(Node current, EvaluationContext ec) {
376 | 0 |
             ec.node = current;
377 | 0 |
             evaluationContextStack.push(ec);
378 | 0 |
         }
379 |  |
380 |  |
         /**
381 |  |
          * @return the peek evaluation context.
382 |  |
          */
383 |  |
         private EvaluationContext getContext() {
384 | 0 |
             return evaluationContextStack.peek();
385 |  |
         }
386 |  |
387 |  |
         /**
388 |  |
          * Pops out the peek evaluation context if ancestor of current node.
389 |  |
          *
390 |  |
          * @param current current node.
391 |  |
          */
392 |  |
         private void popContext(Node current) {
393 | 0 |
             final Node peekNode = evaluationContextStack.peek().node;
394 | 0 |
             if(DomUtils.isAncestorOf(peekNode, current)) {
395 | 0 |
                 evaluationContextStack.pop();
396 |  |
             }
397 | 0 |
         }
398 |  |
399 |  |
         /**
400 |  |
          * Pushes a new vocabulary definition.
401 |  |
          *
402 |  |
          * @param currentNode node proving the vocabulary.
403 |  |
          * @param vocab the vocabulary URI.
404 |  |
          */
405 |  |
         private void pushVocabulary(Node currentNode, URI vocab) {
406 | 0 |
             vocabularyStack.push( new Vocabulary(currentNode, vocab) );
407 | 0 |
         }
408 |  |
409 |  |
         /**
410 |  |
          * @return the current peek vocabulary.
411 |  |
          */
412 |  |
         private URI getVocabulary() {
413 | 0 |
             if(vocabularyStack.isEmpty()) return null;
414 | 0 |
             return vocabularyStack.peek().prefix;
415 |  |
         }
416 |  |
417 |  |
         /**
418 |  |
          * Pops out the vocabulary definition.
419 |  |
          *
420 |  |
          * @param current
421 |  |
          */
422 |  |
         private void popVocabulary(Node current) {
423 | 0 |
             if(vocabularyStack.isEmpty()) return;
424 | 0 |
             if(DomUtils.isAncestorOf(current, vocabularyStack.peek().originatingNode)) {
425 | 0 |
                 vocabularyStack.pop();
426 |  |
             }
427 | 0 |
         }
428 |  |
429 |  |
         /**
430 |  |
          * Purge all incomplete triples originated from a node that is descendant of <code>current</code>.
431 |  |
          *
432 |  |
          * @param current
433 |  |
          */
434 |  |
         private void purgeIncompleteTriples(Node current) {
435 | 0 |
             final List<IncompleteTriple> toBePurged = new ArrayList<IncompleteTriple>();
436 | 0 |
             for(IncompleteTriple incompleteTriple : listOfIncompleteTriples) {
437 | 0 |
                 if( DomUtils.isAncestorOf(current, incompleteTriple.originatingNode, true) ) {
438 | 0 |
                     toBePurged.add(incompleteTriple);
439 |  |
                 }
440 |  |
             }
441 | 0 |
             listOfIncompleteTriples.removeAll(toBePurged);
442 | 0 |
             toBePurged.clear();
443 | 0 |
         }
444 |  |
445 |  |
         /**
446 |  |
          * Reports an error to the error reporter.
447 |  |
          *
448 |  |
          * @param n originating node.
449 |  |
          * @param msg human readable message.
450 |  |
          */
451 |  |
         private void reportError(Node n, String msg) {
452 | 0 |
             final String errorMsg = String.format(
453 |  |
                     "Error while processing node [%s] : '%s'",
454 |  |
                     DomUtils.getXPathForNode(n), msg
455 |  |
             );
456 | 0 |
             final int[] errorLocation = DomUtils.getNodeLocation(n);
457 | 0 |
             this.errorReporter.notifyError(
458 |  |
                     ErrorReporter.ErrorLevel.WARN,
459 |  |
                     errorMsg,
460 |  |
                     errorLocation == null ? -1 : errorLocation[0],
461 |  |
                     errorLocation == null ? -1 : errorLocation[1]
462 |  |
             );
463 | 0 |
         }
464 |  |
465 |  |
         /**
466 |  |
          * Performs a <i>deep-first</i> tree visit on the given root node.
467 |  |
          *
468 |  |
          * @param node root node.
469 |  |
          * @param extractionResult
470 |  |
          */
471 |  |
         private void depthFirstNode(Node node, ExtractionResult extractionResult) {
472 |  |
             try {
473 | 0 |
                 processNode(node, extractionResult);
474 | 0 |
             } catch (Exception e) {
475 | 0 |
                 if(logger.isDebugEnabled()) logger.debug("Error while processing node.", e);
476 | 0 |
                 reportError(node, e.getMessage());
477 |  |
                 // e.printStackTrace();
478 | 0 |
             }
479 | 0 |
             depthFirstChildren(node.getChildNodes(), extractionResult);
480 | 0 |
             purgeIncompleteTriples(node);
481 | 0 |
         }
482 |  |
483 |  |
         /**
484 |  |
          * Performs a <i>deep-first</i> children list visit.
485 |  |
          *
486 |  |
          * @param nodeList
487 |  |
          * @param extractionResult
488 |  |
          */
489 |  |
         private void depthFirstChildren(NodeList nodeList, ExtractionResult extractionResult) {
490 | 0 |
             for(int i = 0; i < nodeList.getLength(); i++) {
491 | 0 |
                 final Node child = nodeList.item(i);
492 | 0 |
                 depthFirstNode(child, extractionResult);
493 | 0 |
                 popMappings(child);
494 | 0 |
                 popVocabulary(child);
495 | 0 |
                 popContext(child);
496 |  |
             }
497 | 0 |
         }
498 |  |
499 |  |
         /**
500 |  |
          * Writes a triple on the extraction result.
501 |  |
          *
502 |  |
          * @param s
503 |  |
          * @param p
504 |  |
          * @param o
505 |  |
          * @param extractionResult
506 |  |
          */
507 |  |
         private void writeTriple(Resource s, URI p, Value o, ExtractionResult extractionResult) {
508 | 0 |
             if(logger.isTraceEnabled()) logger.trace(String.format("writeTriple(%s %s %s)" , s, p, o));
509 | 0 |
             assert s != null : "subject   is null.";
510 | 0 |
             assert p != null : "predicate is null.";
511 | 0 |
             assert o != null : "object    is null.";
512 | 0 |
             extractionResult.writeTriple(s, p, o);
513 | 0 |
         }
514 |  |
515 |  |
         /**
516 |  |
          * Processes the current node on the extraction algorithm.
517 |  |
          * All the steps of this algorithm are annotated with the
518 |  |
          * specification and section which describes it. The annotation is at form
519 |  |
          * <em>RDFa&lt;spec-version%gt;[&lt;section&gt;]</em>
520 |  |
          *
521 |  |
          * @param currentElement
522 |  |
          * @param extractionResult
523 |  |
          * @throws Exception
524 |  |
          */
525 |  |
         // TODO: add references to the RDFa 1.1 algorithm.
526 |  |
         private void processNode(Node currentElement, ExtractionResult extractionResult) throws Exception {
527 | 0 |
             if(logger.isTraceEnabled()) logger.trace("processNode(" + DomUtils.getXPathForNode(currentElement) + ")");
528 | 0 |
             final EvaluationContext currentEvaluationContext = getContext();
529 |  |
             try {
530 | 0 |
                 if(
531 |  |
                     currentElement.getNodeType() != Node.DOCUMENT_NODE
532 |  |
                     &&
533 |  |
                     currentElement.getNodeType() != Node.ELEMENT_NODE
534 |  |
                 ) return;
535 |  |
536 |  |
                 // RDFa1.1[7.5.3]
537 | 0 |
                 updateVocabulary(currentElement);
538 |  |
539 |  |
                 // RDFa1.0[5.5.2] / RDFa1.1[7.5.4]
540 |  |
                 //Node currentElement = node;
541 | 0 |
                 updateURIMapping(currentElement);
542 |  |
543 |  |
                 // RDFa1.0[5.5.3] / RDFa1.1[7.5.5]
544 | 0 |
                 updateLanguage(currentElement, currentEvaluationContext);
545 |  |
546 | 0 |
                 if(! isRelativeNode(currentElement)) {
547 |  |
                     // RDFa1.0[5.5.4] / RDFa1.1[7.5.6]
548 | 0 |
                     establishNewSubject(currentElement, currentEvaluationContext);
549 |  |
                 } else {
550 |  |
                     // RDFa1.0[5.5.5] / RDFa1.1[7.5.7]
551 | 0 |
                     establishNewSubjectCurrentObjectResource(
552 |  |
                             currentElement,
553 |  |
                             currentEvaluationContext
554 |  |
                     );
555 |  |
                 }
556 |  |
557 |  |
                 /*
558 |  |
                 if(currentEvaluationContext.newSubject == null) {
559 |  |
                     currentEvaluationContext.newSubject = resolveURI(documentBase.toExternalForm());
560 |  |
                 }
561 |  |
                 assert currentEvaluationContext.newSubject != null : "newSubject must be not null.";
562 |  |
                 */
563 | 0 |
                 if(currentEvaluationContext.newSubject == null) return;
564 | 0 |
                 if(logger.isDebugEnabled()) logger.debug("newSubject: " + currentEvaluationContext.newSubject);
565 |  |
566 |  |
                 // RDFa1.0[5.5.6] / RDFa1.1[7.5.8]
567 | 0 |
                 final URI[] types = getTypes(currentElement);
568 | 0 |
                 for(URI type : types) {
569 | 0 |
                     writeTriple(currentEvaluationContext.newSubject, RDF.TYPE, type, extractionResult);
570 |  |
                 }
571 |  |
572 |  |
                 // RDFa1.0[5.5.7] / RDFa1.1[7.5.9]
573 | 0 |
                 final URI[] rels = getRels(currentElement);
574 | 0 |
                 final URI[] revs = getRevs(currentElement);
575 | 0 |
                 if(currentEvaluationContext.currentObjectResource != null) {
576 | 0 |
                     for (URI rel : rels) {
577 | 0 |
                         writeTriple(
578 |  |
                                 currentEvaluationContext.newSubject,
579 |  |
                                 rel,
580 |  |
                                 currentEvaluationContext.currentObjectResource,
581 |  |
                                 extractionResult
582 |  |
                         );
583 |  |
                     }
584 | 0 |
                     for (URI rev : revs) {
585 | 0 |
                         writeTriple(
586 |  |
                                 currentEvaluationContext.currentObjectResource,
587 |  |
                                 rev,
588 |  |
                                 currentEvaluationContext.newSubject, extractionResult
589 |  |
                         );
590 |  |
                     }
591 |  |
                 } else { // RDFa1.0[5.5.8] / RDFa1.1[7.5.10]
592 | 0 |
                     for(URI rel : rels) {
593 | 0 |
                         listOfIncompleteTriples.add(
594 |  |
                                 new IncompleteTriple(
595 |  |
                                         currentElement,
596 |  |
                                         currentEvaluationContext.newSubject,
597 |  |
                                         rel,
598 |  |
                                         IncompleteTripleDirection.Forward
599 |  |
                                 )
600 |  |
                         );
601 |  |
                     }
602 | 0 |
                     for(URI rev : revs) {
603 | 0 |
                         listOfIncompleteTriples.add(
604 |  |
                                 new IncompleteTriple(
605 |  |
                                         currentElement,
606 |  |
                                         currentEvaluationContext.newSubject,
607 |  |
                                         rev,
608 |  |
                                         IncompleteTripleDirection.Reverse
609 |  |
                                 )
610 |  |
                         );
611 |  |
                     }
612 |  |
                 }
613 |  |
614 |  |
                 // RDFa1.0[5.5.9] / RDFa1.1[7.5.11]
615 | 0 |
                 final Value currentObject = getCurrentObject(currentElement);
616 | 0 |
                 final URI[] predicates = getPredicate(currentElement);
617 | 0 |
                 if (currentObject != null && predicates != null) {
618 | 0 |
                     for (URI predicate : predicates) {
619 | 0 |
                         writeTriple(currentEvaluationContext.newSubject, predicate, currentObject, extractionResult);
620 |  |
                     }
621 |  |
                 }
622 |  |
623 |  |
                 // RDFa1.0[5.5.10] / RDFa1.1[7.5.12]
624 | 0 |
                 if(!currentEvaluationContext.skipElem && currentEvaluationContext.newSubject != null) {
625 | 0 |
                     for (IncompleteTriple incompleteTriple : listOfIncompleteTriples) {
626 | 0 |
                         incompleteTriple.produceTriple(
627 |  |
                                 currentElement,
628 |  |
                                 currentEvaluationContext.newSubject,
629 |  |
                                 extractionResult
630 |  |
                         );
631 |  |
                     }
632 |  |
                 }
633 | 0 |
             } catch (Exception e) {
634 | 0 |
                 throw e;
635 |  |
             } finally {
636 |  |
                 // RDFa1.0[5.5.11] / RDFa1.1[7.5.13]
637 | 0 |
                 if(currentEvaluationContext.recourse) {
638 | 0 |
                     EvaluationContext newEvaluationContext = new EvaluationContext(currentEvaluationContext.base);
639 | 0 |
                     if(currentEvaluationContext.skipElem) {
640 | 0 |
                         newEvaluationContext.language = currentEvaluationContext.language;
641 |  |
                     } else {
642 | 0 |
                         newEvaluationContext.base = currentEvaluationContext.base;
643 |  |
644 | 0 |
                         if(currentEvaluationContext.newSubject != null) {
645 | 0 |
                             newEvaluationContext.parentSubject = currentEvaluationContext.newSubject;
646 |  |
                         } else {
647 | 0 |
                             newEvaluationContext.parentSubject = currentEvaluationContext.parentSubject;
648 |  |
                         }
649 |  |
650 | 0 |
                         if(currentEvaluationContext.currentObjectResource != null) {
651 | 0 |
                             newEvaluationContext.parentObject = currentEvaluationContext.currentObjectResource;
652 | 0 |
                         } else if(currentEvaluationContext.newSubject != null) {
653 | 0 |
                             newEvaluationContext.parentObject = currentEvaluationContext.newSubject;
654 |  |
                         } else {
655 | 0 |
                             newEvaluationContext.parentObject = currentEvaluationContext.parentSubject;
656 |  |
                         }
657 |  |
658 | 0 |
                         newEvaluationContext.language = currentEvaluationContext.language;
659 |  |
                     }
660 | 0 |
                     pushContext(currentElement, newEvaluationContext);
661 | 0 |
                 }
662 |  |
             }
663 | 0 |
         }
664 |  |
665 |  |
         /**
666 |  |
          * Extract URI namespaces (prefixes) from the current node.
667 |  |
          *
668 |  |
          * @param node
669 |  |
          * @param prefixMapList
670 |  |
          */
671 |  |
         private void extractPrefixes(Node node, List<PrefixMap> prefixMapList) {
672 | 0 |
             final String prefixAttribute = DomUtils.readAttribute(node, PREFIX_ATTRIBUTE, null);
673 | 0 |
             if(prefixAttribute == null) return;
674 | 0 |
             final String[] prefixParts = extractPrefixSections(prefixAttribute);
675 | 0 |
             for(String prefixPart : prefixParts) {
676 | 0 |
                 int splitPoint = prefixPart.indexOf(URI_PREFIX_SEPARATOR);
677 | 0 |
                 final String prefix = prefixPart.substring(0, splitPoint);
678 | 0 |
                 if(prefix.length() == 0) {
679 | 0 |
                     reportError(node, String.format("Invalid prefix length in prefix attribute '%s'", prefixAttribute));
680 | 0 |
                     continue;
681 |  |
                 }
682 |  |
                 final URI uri;
683 | 0 |
                 final String uriStr = prefixPart.substring(splitPoint + 1);
684 |  |
                 try {
685 | 0 |
                     uri = resolveURI(uriStr);
686 | 0 |
                 } catch (Exception e) {
687 | 0 |
                     reportError(
688 |  |
                             node,
689 |  |
                             String.format(
690 |  |
                                     "Resolution of prefix '%s' defines an invalid URI: '%s'",
691 |  |
                                     prefixAttribute, uriStr
692 |  |
                             )
693 |  |
                     );
694 | 0 |
                     continue;
695 | 0 |
                 }
696 | 0 |
                 prefixMapList.add( new PrefixMap(prefix, uri) );
697 |  |
             }
698 | 0 |
         }
699 |  |
700 |  |
         /**
701 |  |
          * Updates the current language.
702 |  |
          *
703 |  |
          * @param node
704 |  |
          * @param currentEvaluationContext
705 |  |
          */
706 |  |
         private void updateLanguage(Node node, EvaluationContext currentEvaluationContext) {
707 | 0 |
             final String candidateLanguage = DomUtils.readAttribute(node, XML_LANG_ATTRIBUTE, null);
708 | 0 |
             if(candidateLanguage != null) currentEvaluationContext.language = candidateLanguage;
709 | 0 |
         }
710 |  |
711 |  |
         /**
712 |  |
          * Establish the new subject for the current recursion.
713 |  |
          * See <i>RDFa 1.0 Specification section 5.5.4</i>, <i>RDFa 1.1 Specification section 7.5.6</i>.
714 |  |
          *
715 |  |
          * @param node
716 |  |
          * @param currentEvaluationContext
717 |  |
          * @throws URISyntaxException
718 |  |
          */
719 |  |
         private void establishNewSubject(Node node, EvaluationContext currentEvaluationContext)
720 |  |
         throws URISyntaxException {
721 |  |
             String candidateURIOrCURIE;
722 | 0 |
             for(String subjectAttribute : SUBJECT_ATTRIBUTES) {
723 | 0 |
                 candidateURIOrCURIE = DomUtils.readAttribute(node, subjectAttribute, null);
724 | 0 |
                 if(candidateURIOrCURIE != null) {
725 | 0 |
                     currentEvaluationContext.newSubject = resolveCURIEOrURI(candidateURIOrCURIE, false);
726 | 0 |
                     return;
727 |  |
                 }
728 |  |
             }
729 |  |
730 | 0 |
             if(node.getNodeName().equalsIgnoreCase(HEAD_TAG) || node.getNodeName().equalsIgnoreCase(BODY_TAG)) {
731 | 0 |
                 currentEvaluationContext.newSubject = resolveURI(currentEvaluationContext.base.toString());
732 | 0 |
                 return;
733 |  |
             }
734 |  |
735 | 0 |
             if(DomUtils.hasAttribute(node, TYPEOF_ATTRIBUTE)) {
736 | 0 |
                 currentEvaluationContext.newSubject = RDFUtils.bnode();
737 | 0 |
                 return;
738 |  |
             }
739 |  |
740 | 0 |
             if(DomUtils.hasAttribute(node, PROPERTY_ATTRIBUTE)) {
741 | 0 |
                 currentEvaluationContext.skipElem = true;
742 |  |
             }
743 | 0 |
             if(currentEvaluationContext.parentObject != null) {
744 | 0 |
                 currentEvaluationContext.newSubject = (Resource) currentEvaluationContext.parentObject;
745 | 0 |
                 return;
746 |  |
             }
747 |  |
748 | 0 |
             currentEvaluationContext.newSubject = null;
749 | 0 |
         }
750 |  |
751 |  |
         /**
752 |  |
          * Establishes the new subject and the current object resource.
753 |  |
          *
754 |  |
          * See <i>RDFa 1.0 Specification section 5.5.5</i>, <i>RDFa 1.1 Specification section 7.5.7</i>.
755 |  |
          *
756 |  |
          * @param node
757 |  |
          * @param currentEvaluationContext
758 |  |
          * @throws URISyntaxException
759 |  |
          */
760 |  |
         private void establishNewSubjectCurrentObjectResource(Node node, EvaluationContext currentEvaluationContext)
761 |  |
         throws URISyntaxException {
762 |  |
             // Subject.
763 |  |
             String candidateURIOrCURIE;
764 | 0 |
             candidateURIOrCURIE = DomUtils.readAttribute(node, ABOUT_ATTRIBUTE, null);
765 | 0 |
             if(candidateURIOrCURIE != null) {
766 | 0 |
                 currentEvaluationContext.newSubject = resolveCURIEOrURI(candidateURIOrCURIE, false);
767 |  |
             } else {
768 | 0 |
                 candidateURIOrCURIE = DomUtils.readAttribute(node, SRC_ATTRIBUTE, null);
769 | 0 |
                 if (candidateURIOrCURIE != null) {
770 | 0 |
                     currentEvaluationContext.newSubject = resolveURI(candidateURIOrCURIE);
771 |  |
                 } else {
772 | 0 |
                     if (node.getNodeName().equalsIgnoreCase(HEAD_TAG) || node.getNodeName().equalsIgnoreCase(BODY_TAG)) {
773 | 0 |
                         currentEvaluationContext.newSubject = resolveURI(currentEvaluationContext.base.toString());
774 |  |
                     } else {
775 | 0 |
                         if (DomUtils.hasAttribute(node, TYPEOF_ATTRIBUTE)) {
776 | 0 |
                             currentEvaluationContext.newSubject = RDFUtils.bnode();
777 |  |
                         } else {
778 | 0 |
                             if (currentEvaluationContext.parentObject != null) {
779 | 0 |
                                 currentEvaluationContext.newSubject = (Resource) currentEvaluationContext.parentObject;
780 |  |
                             }
781 |  |
                         }
782 |  |
                     }
783 |  |
                 }
784 |  |
             }
785 |  |
786 |  |
             // Object.
787 | 0 |
             candidateURIOrCURIE = DomUtils.readAttribute(node, RESOURCE_ATTRIBUTE, null);
788 | 0 |
             if(candidateURIOrCURIE != null) {
789 | 0 |
                 currentEvaluationContext.currentObjectResource = resolveCURIEOrURI(candidateURIOrCURIE, false);
790 | 0 |
                 return;
791 |  |
             }
792 |  |
793 | 0 |
             candidateURIOrCURIE = DomUtils.readAttribute(node, HREF_ATTRIBUTE, null);
794 | 0 |
             if(candidateURIOrCURIE != null) {
795 | 0 |
                 currentEvaluationContext.currentObjectResource = resolveURI(candidateURIOrCURIE);
796 | 0 |
                 return;
797 |  |
             }
798 | 0 |
             currentEvaluationContext.currentObjectResource = null;
799 | 0 |
         }
800 |  |
801 |  |
         private URI[] getTypes(Node node) throws URISyntaxException {
802 | 0 |
             final String typeOf = DomUtils.readAttribute(node, TYPEOF_ATTRIBUTE, null);
803 | 0 |
             return resolveCurieOrURIList(node, typeOf, true);
804 |  |
         }
805 |  |
806 |  |
         private URI[] getRels(Node node) throws URISyntaxException {
807 | 0 |
             final String rel = DomUtils.readAttribute(node, REL_ATTRIBUTE, null);
808 | 0 |
             return resolveCurieOrURIList(node, rel, true);
809 |  |
         }
810 |  |
811 |  |
         private URI[] getRevs(Node node) throws URISyntaxException {
812 | 0 |
             final String rev = DomUtils.readAttribute(node, REV_ATTRIBUTE, null);
813 | 0 |
             return resolveCurieOrURIList(node, rev, true);
814 |  |
         }
815 |  |
816 |  |
         private URI[] getPredicate(Node node) throws URISyntaxException {
817 | 0 |
             final String candidateURI = DomUtils.readAttribute(node, PROPERTY_ATTRIBUTE, null);
818 | 0 |
             if(candidateURI == null) return null;
819 | 0 |
             return resolveCurieOrURIList(node, candidateURI, true);
820 |  |
         }
821 |  |
822 |  |
         /**
823 |  |
          * Establishes the new object value.
824 |  |
          * See <i>RDFa 1.0 Specification section 5.5.9</i>, <i>RDFa 1.1 Specification section 7.5.11</i>.
825 |  |
          *
826 |  |
          * @param node
827 |  |
          * @return
828 |  |
          * @throws URISyntaxException
829 |  |
          * @throws IOException
830 |  |
          * @throws TransformerException
831 |  |
          */
832 |  |
         private Value getCurrentObject(Node node)
833 |  |
         throws URISyntaxException, IOException, TransformerException {
834 | 0 |
             final String candidateObject = DomUtils.readAttribute(node, HREF_ATTRIBUTE, null);
835 | 0 |
             if(candidateObject != null) {
836 | 0 |
                 return resolveURI(candidateObject);
837 |  |
             } else {
838 | 0 |
                 return gerCurrentObjectLiteral(node);
839 |  |
             }
840 |  |
         }
841 |  |
842 |  |
         private Literal gerCurrentObjectLiteral(Node node)
843 |  |
         throws URISyntaxException, IOException, TransformerException {
844 | 0 |
             final EvaluationContext currentEvaluationContext = getContext();
845 |  |
             Literal literal;
846 |  |
847 | 0 |
             literal = getAsTypedLiteral(node);
848 | 0 |
             if(literal != null) return literal;
849 |  |
850 | 0 |
             literal = getAsXMLLiteral(node);
851 | 0 |
             if(literal != null) {
852 | 0 |
                 currentEvaluationContext.recourse = false;
853 | 0 |
                 return literal;
854 |  |
             }
855 |  |
856 | 0 |
             literal = getAsPlainLiteral(node, currentEvaluationContext.language);
857 | 0 |
             if(literal != null) return literal;
858 |  |
859 | 0 |
             return null;
860 |  |
         }
861 |  |
862 |  |
         private static String getNodeContent(Node node) {
863 | 0 |
             final String candidateContent = DomUtils.readAttribute(node, CONTENT_ATTRIBUTE, null);
864 | 0 |
             if(candidateContent != null) return candidateContent;
865 | 0 |
             return node.getTextContent();
866 |  |
         }
867 |  |
868 |  |
         /**
869 |  |
          * Extracts the current typed literal from the given node.
870 |  |
          * See <i>RDFa 1.0 Specification section 5.5.9.1</i>.
871 |  |
          *
872 |  |
          * @param node
873 |  |
          * @return
874 |  |
          * @throws URISyntaxException
875 |  |
          */
876 |  |
         private Literal getAsTypedLiteral(Node node) throws URISyntaxException {
877 | 0 |
             final String datatype = DomUtils.readAttribute(node, DATATYPE_ATTRIBUTE, null);
878 | 0 |
             if (datatype == null || datatype.trim().length() == 0 || XML_LITERAL_DATATYPE.equals(datatype.trim()) ) {
879 | 0 |
                 return null;
880 |  |
             }
881 | 0 |
             final Resource curieOrURI = resolveCURIEOrURI(datatype, true);
882 | 0 |
             return RDFUtils.literal(getNodeContent(node), curieOrURI instanceof URI ? (URI) curieOrURI : null);
883 |  |
         }
884 |  |
885 |  |
         private void pushMappings(Node sourceNode, List<PrefixMap> prefixMapList) {
886 | 0 |
             logger.trace("pushMappings()");
887 |  |
888 | 0 |
             final Map<String, URI> mapping = new HashMap<String, URI>();
889 | 0 |
             for (PrefixMap prefixMap : prefixMapList) {
890 | 0 |
                 mapping.put(prefixMap.prefix, prefixMap.uri);
891 |  |
             }
892 | 0 |
             uriMappingStack.push( new URIMapping(sourceNode, mapping) );
893 | 0 |
         }
894 |  |
895 |  |
         private void popMappings(Node node) {
896 | 0 |
             if(uriMappingStack.isEmpty()) return;
897 | 0 |
             final URIMapping peek = uriMappingStack.peek();
898 | 0 |
             if( ! DomUtils.isAncestorOf(peek.sourceNode, node) ) {
899 | 0 |
                 logger.trace("popMappings()");
900 | 0 |
                 uriMappingStack.pop();
901 |  |
             }
902 | 0 |
         }
903 |  |
904 |  |
         /**
905 |  |
          * Resolve a namespaced URI, if <code>safe</code> is <code>true</code>
906 |  |
          * then the mapping must define a prefix, otherwise it is considered relative.
907 |  |
          *
908 |  |
          * @param mapping
909 |  |
          * @param resolutionPolicy
910 |  |
          * @return
911 |  |
          */
912 |  |
         private Resource resolveNamespacedURI(String mapping, ResolutionPolicy resolutionPolicy) {
913 | 0 |
             if(mapping.indexOf(URI_PATH_SEPARATOR) == 0) { // Begins with '/'
914 | 0 |
                 mapping = mapping.substring(1);
915 |  |
             }
916 |  |
917 | 0 |
             final int prefixSeparatorIndex = mapping.indexOf(':');
918 | 0 |
             if(prefixSeparatorIndex == -1) { // there is no prefix separator.
919 | 0 |
                 if(resolutionPolicy == ResolutionPolicy.NSRequired) {
920 | 0 |
                     throw new IllegalArgumentException(
921 |  |
                             String.format("Invalid mapping string [%s], must declare a prefix.", mapping)
922 |  |
                     );
923 |  |
                 }
924 | 0 |
                 if (resolutionPolicy == ResolutionPolicy.TermAllowed) {
925 | 0 |
                     final URI currentVocabulary = getVocabulary();
926 |  |
                     // Mapping is a TERM.
927 | 0 |
                     if (currentVocabulary != null) {
928 | 0 |
                         return resolveURI(currentVocabulary.toString() + mapping);
929 |  |
                     }
930 |  |
                 }
931 | 0 |
                 return resolveURI(documentBase.toString() + mapping);
932 |  |
             }
933 |  |
934 | 0 |
             final String prefix = mapping.substring(0, prefixSeparatorIndex);
935 | 0 |
             final URI curieMapping = getMapping(prefix);
936 | 0 |
             if(curieMapping == null) {
937 | 0 |
                 throw new IllegalArgumentException( String.format("Cannot map prefix '%s'", prefix) );
938 |  |
             }
939 | 0 |
             final String candidateCURIEStr = curieMapping.toString() + mapping.substring(prefixSeparatorIndex + 1);
940 |  |
             final java.net.URI candidateCURIE;
941 |  |
             try {
942 | 0 |
                 candidateCURIE = new java.net.URI(candidateCURIEStr);
943 | 0 |
             } catch (URISyntaxException urise) {
944 | 0 |
                 throw new IllegalArgumentException(String.format("Invalid CURIE '%s'", candidateCURIEStr) );
945 | 0 |
             }
946 | 0 |
             return resolveURI(
947 |  |
                     candidateCURIE.isAbsolute()
948 |  |
                             ?
949 |  |
                             candidateCURIE.toString()
950 |  |
                             :
951 |  |
                             documentBase.toString() + candidateCURIE.toString()
952 |  |
             );
953 |  |
         }
954 |  |
955 |  |
         /**
956 |  |
          * The resolution policy provided to the method {@link #resolveNamespacedURI(String, ResolutionPolicy)}.
957 |  |
          */
958 | 0 |
         enum ResolutionPolicy {
959 | 0 |
             NSNotRequired,
960 | 0 |
             NSRequired,
961 | 0 |
             TermAllowed
962 |  |
         }
963 |  |
964 |  |
         /**
965 |  |
          * Defines an evaluation context.
966 |  |
          */
967 | 0 |
         private class EvaluationContext {
968 |  |
             private Node node;
969 |  |
             private URL base;
970 |  |
             private Resource parentSubject;
971 |  |
             private Value parentObject;
972 |  |
             private String language;
973 |  |
             private boolean recourse;
974 |  |
             private boolean skipElem;
975 |  |
             private Resource newSubject;
976 |  |
             private Resource currentObjectResource;
977 |  |
978 |  |
             /**
979 |  |
              * Sections <em>RDFa1.0[5.5]</em>, <em>RDFa1.0[5.5.1]</em>, <em>RDFa1.1[7.5.1]</em> .
980 |  |
              *
981 |  |
              * @param base
982 |  |
              */
983 | 0 |
             EvaluationContext(URL base) {
984 | 0 |
                 this.base             = base;
985 | 0 |
                 this.parentSubject    = resolveURI( base.toExternalForm() );
986 | 0 |
                 this.parentObject     = null;
987 | 0 |
                 this.language         = null;
988 | 0 |
                 this.recourse         = true;
989 | 0 |
                 this.skipElem         = false;
990 | 0 |
                 this.newSubject       = null;
991 | 0 |
                 this.currentObjectResource = null;
992 | 0 |
             }
993 |  |
         }
994 |  |
995 |  |
         /**
996 |  |
          * Defines a prefix mapping.
997 |  |
          */
998 |  |
         private class PrefixMap {
999 |  |
             final String prefix;
1000 |  |
             final URI    uri;
1001 | 0 |
             public PrefixMap(String prefix, URI uri) {
1002 | 0 |
                 this.prefix = prefix;
1003 | 0 |
                 this.uri = uri;
1004 | 0 |
             }
1005 |  |
         }
1006 |  |
1007 |  |
         /**
1008 |  |
          * Defines a URI mapping.
1009 |  |
          */
1010 |  |
         private class URIMapping {
1011 |  |
             final Node sourceNode;
1012 |  |
             final Map<String, URI> map;
1013 |  |
1014 | 0 |
             public URIMapping(Node sourceNode, Map<String, URI> map) {
1015 | 0 |
                 this.sourceNode = sourceNode;
1016 | 0 |
                 this.map        = map;
1017 | 0 |
             }
1018 |  |
         }
1019 |  |
1020 |  |
         /**
1021 |  |
          * Defines the direction of an {@link IncompleteTriple}.
1022 |  |
          */
1023 | 0 |
         private enum IncompleteTripleDirection {
1024 | 0 |
             Forward,
1025 | 0 |
             Reverse
1026 |  |
         }
1027 |  |
1028 |  |
         /**
1029 |  |
          * Defines an incomplete triple.
1030 |  |
          */
1031 |  |
         private class IncompleteTriple {
1032 |  |
             final Node     originatingNode;
1033 |  |
             final Resource subject;
1034 |  |
             final URI      predicate;
1035 |  |
             final IncompleteTripleDirection direction;
1036 |  |
1037 |  |
             public IncompleteTriple(
1038 |  |
                     Node originatingNode,
1039 |  |
                     Resource subject,
1040 |  |
                     URI predicate,
1041 |  |
                     IncompleteTripleDirection direction
1042 | 0 |
             ) {
1043 | 0 |
                 if(originatingNode == null || subject == null || predicate == null || direction == null)
1044 | 0 |
                     throw new IllegalArgumentException();
1045 |  |
1046 | 0 |
                 this.originatingNode = originatingNode;
1047 | 0 |
                 this.subject         = subject;
1048 | 0 |
                 this.predicate       = predicate;
1049 | 0 |
                 this.direction       = direction;
1050 | 0 |
             }
1051 |  |
1052 |  |
             public boolean produceTriple(Node resourceNode, Resource r, ExtractionResult extractionResult) {
1053 | 0 |
                 if( ! DomUtils.isAncestorOf(originatingNode, resourceNode, true) ) return false;
1054 |  |
1055 | 0 |
                 if(r == null) throw new IllegalArgumentException();
1056 | 0 |
                 switch (direction) {
1057 |  |
                     case Forward:
1058 | 0 |
                         extractionResult.writeTriple(subject, predicate, r);
1059 | 0 |
                         break;
1060 |  |
                     case Reverse:
1061 | 0 |
                         extractionResult.writeTriple(r, predicate, subject);
1062 | 0 |
                         break;
1063 |  |
                     default:
1064 | 0 |
                         throw new IllegalStateException();
1065 |  |
                 }
1066 | 0 |
                 return true;
1067 |  |
             }
1068 |  |
         }
1069 |  |
1070 |  |
         /**
1071 |  |
          * Defines a vocabulary object.
1072 |  |
          */
1073 |  |
         private class Vocabulary {
1074 |  |
             final Node originatingNode;
1075 |  |
             final URI prefix;
1076 |  |
1077 | 0 |
             public Vocabulary(Node originatingNode, URI prefix) {
1078 | 0 |
                 this.originatingNode = originatingNode;
1079 | 0 |
                 this.prefix = prefix;
1080 | 0 |
             }
1081 |  |
         }
1082 |  |
1083 |  |
     }