| /******************************************************************************* |
| * 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.microdata; |
| |
| import org.apache.http.NameValuePair; |
| import org.apache.sling.hapi.client.*; |
| import org.apache.sling.hapi.client.forms.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); |
| } |
| } |
| |
| } |