blob: c60ca9786bae7f63235279c2ca77becada70c5a5 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
******************************************************************************/
package org.apache.sling.hapi.client.impl.microdata;
import org.apache.http.NameValuePair;
import org.apache.sling.hapi.client.*;
import org.apache.sling.hapi.client.forms.internal.FormValues;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
public class MicrodataDocument implements Document {
private org.jsoup.nodes.Document jsoupDocument;
private HtmlClient client;
public MicrodataDocument(String html, HtmlClient client, String baseUrl) {
this.jsoupDocument = Jsoup.parse(html, baseUrl);
this.client = client;
}
@Override
public Items link(String rel) throws ClientException {
Item me = toItem(jsoupDocument);
return me.link(rel);
}
@Override
public Items form(String rel) throws ClientException {
Item me = toItem(jsoupDocument);
return me.form(rel);
}
@Override
public Items item(String rel) {
List<Item> items = new ArrayList<Item>();
for (Item i : items()) {
if (((ItemImpl) i).el.hasClass(rel)) {
items.add(i);
}
}
return new ItemsImpl(items);
}
@Override
public Items items() {
return new ItemsImpl(selectItems(jsoupDocument, new ArrayList<Item>()));
}
@Override
public String toString() {
return this.jsoupDocument.toString();
}
private String toClass(String name) {
return "." + name;
}
private List<Item> toItems(Elements el) {
List<Item> items = new ArrayList<Item>();
for (Element e : el) {
items.add(toItem(e));
}
return items;
}
private Item toItem(Element el) {
return new ItemImpl(el, this);
}
private List<Item> selectItems(Element e, List<Item> items) {
if (e.hasAttr("itemscope") && !e.hasAttr("itemprop")) {
items.add(new ItemImpl(e, this));
return items;
}
for (Element c : e.children()) {
selectItems(c, items);
}
return items;
}
private class ItemImpl implements Item {
private Element el;
private MicrodataDocument document;
private ItemImpl followed;
public ItemImpl(Element element, MicrodataDocument document) {
if (element == null) throw new NullPointerException("element is mandatory");
this.el = element;
this.document = document;
}
@Override
public Items prop(String name) throws ClientException {
return new ItemsImpl(selectProps(getProxy().el, name, new ArrayList<Item>()));
}
@Override
public Set<String> props() throws ClientException {
return selectAllPropNames(getProxy().el, new HashSet<String>());
}
@Override
public Items link(String rel) throws ClientException {
String selector = "link[rel=" + rel + "], a[rel=" + rel + "], link" + toClass(rel) + ", a" + toClass(rel);
return new ItemsImpl(toItems(getProxy().el.select(selector)));
}
@Override
public Items link() throws ClientException {
String selector = "link, a";
return new ItemsImpl(toItems(getProxy().el.select(selector)));
}
@Override
public Items form(String rel) throws ClientException {
String selector = "form[data-rel=" + rel + "], form" + toClass(rel);
return new ItemsImpl(toItems(getProxy().el.select(selector)));
}
@Override
public Items form() throws ClientException {
String selector = "form";
return new ItemsImpl(toItems(getProxy().el.select(selector)));
}
/* Private methods */
private List<Item> selectProps(Element e, String name, List<Item> items) {
for (Element c : e.children()) {
if (c.hasAttr("itemprop")) {
if (c.attr("itemprop").equals(name)) {
items.add(new ItemImpl(c, document));
}
if (c.hasAttr("itemscope")) {
continue;
}
}
selectProps(c, name, items);
}
return items;
}
private Set<String> selectAllPropNames(Element e, Set<String> items) {
for (Element c : e.children()) {
if (c.hasAttr("itemprop")) {
items.add(c.attr("itemprop"));
}
if (c.hasAttr("itemscope")) {
continue;
}
selectAllPropNames(c, items);
}
return items;
}
private ItemImpl getProxy() throws ClientException {
if (el.tagName().equalsIgnoreCase("a") && el.hasAttr("href")) {
if (followed == null) {
followed = follow(el, document);
}
return followed;
}
return this;
}
private ItemImpl follow(Element el, MicrodataDocument doc) throws ClientException {
if (el.hasAttr("itemscope")) {
return new ItemImpl(el, doc);
}
if (el.tagName().equalsIgnoreCase("a") && el.hasAttr("href")) {
String href = el.attr("href");
if (href.startsWith("#")) {
Element first = el.ownerDocument().select(href).first();
return first == null ? null : follow(first, doc);
}
String absHref = el.attr("abs:href");
MicrodataDocument d = (MicrodataDocument) doc.client.get(absHref);
try {
URI uri = new URI(absHref);
String fragment = uri.getRawFragment();
if (fragment != null) {
Element e = d.jsoupDocument.getElementById(fragment);
return e == null ? null : follow(e, d);
}
} catch (URISyntaxException ex) {
throw new ClientException("Error parsing URI: " + absHref, ex);
}
ItemsImpl items = (ItemsImpl) d.items();
if (items.length() == 1) {
return (ItemImpl) items.at(0);
}
throw new ClientException("Unable determine item: " + absHref);
}
return new ItemImpl(el, doc);
}
@Override
public String text() throws ClientException {
// resolve element
Element el = getProxy().el;
// if it's a meta, get the value of the content attr
if (el.tagName().equalsIgnoreCase("meta") && el.hasAttr("content")) {
return el.attr("content");
}
// else, get the text value using jsoup
return getProxy().el.text();
}
@Override
public boolean bool() throws ClientException {
return Boolean.parseBoolean(text());
}
@Override
public int number() throws ClientException {
return Integer.parseInt(text());
}
@Override
public String href() {
return el.attr("href");
}
@Override
public String src() {
return el.attr("src");
}
@Override
public Document follow() throws ClientException {
if ((el.tagName().equalsIgnoreCase("a") || el.tagName().equalsIgnoreCase("link")) && el.hasAttr("href")) {
String href = el.attr("href");
if (href.startsWith("#")) {
return document;
}
return document.client.enter(el.attr("abs:href"));
}
throw new ClientException("Unable to follow: " + el.toString());
}
@Override
public Document submit(Iterable<NameValuePair> values) throws ClientException {
if (el.tagName().equalsIgnoreCase("form")) {
String action = el.attr("abs:action");
if (action.length() == 0) {
action = el.baseUri();
}
String method = el.attr("method");
if (method.length() == 0 || method.equalsIgnoreCase("get")) {
FormValues query = new FormValues(el, values);
String url = action + (action.contains("?") ? "?" : "&") + query.toString();
return document.client.enter(url);
}
if (method.equalsIgnoreCase("post")) {
String enctype = el.attr("enctype");
FormValues v = new FormValues(el, values);
if (enctype.length() == 0 || enctype.equalsIgnoreCase("application/x-www-form-urlencoded")) {
return document.client.post(action, v.toUrlEncodedEntity());
} else if (enctype.equalsIgnoreCase("multipart/form-data")) {
return document.client.post(action, v.toMultipartEntity());
}
throw new ClientException("Unsupported form enctype: " + enctype);
}
throw new ClientException("Unsupported form method: " + method);
}
throw new ClientException("The item is not a form");
}
}
/**
* Items impl for microdata
*/
private class ItemsImpl implements Items {
private List<Item> items;
public ItemsImpl(List<Item> items) {
this.items = items;
}
@Override
public Iterator<Item> iterator() {
return items.iterator();
}
@Override
public Item at(int index) {
return items.get(index);
}
@Override
public int length() {
return items.size();
}
@Override
public Items prop(String name) throws ClientException {
return items.get(0).prop(name);
}
@Override
public Set<String> props() throws ClientException {
return items.get(0).props();
}
@Override
public Items link(String rel) throws ClientException {
return items.get(0).link(rel);
}
@Override
public Items link() throws ClientException {
return items.get(0).link();
}
@Override
public Items form(String rel) throws ClientException {
return items.get(0).form(rel);
}
@Override
public Items form() throws ClientException {
return items.get(0).form();
}
@Override
public String text() throws ClientException {
return items.get(0).text();
}
@Override
public boolean bool() throws ClientException {
return items.get(0).bool();
}
@Override
public int number() throws ClientException {
return items.get(0).number();
}
@Override
public String href() {
return items.get(0).href();
}
@Override
public String src() {
return items.get(0).src();
}
@Override
public Document follow() throws ClientException {
return items.get(0).follow();
}
@Override
public Document submit(Iterable<NameValuePair> values) throws ClientException {
return items.get(0).submit(values);
}
}
}