package cz.cuni.mff.inetpaint.svgwb;
import java.io.ByteArrayInputStream;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.xmlpull.v1.XmlPullParser;
import org.w3c.dom.*;
import javax.xml.parsers.*;
/**
* Provider parsující {@link SVGWBExtension} z paketů.
*
* @see PacketExtension
* @see PacketExtensionProvider
*
* @author Jindřich Helcl
*/
public class SVGWBExtensionProvider implements PacketExtensionProvider {
XmlPullParser parser;
/**
* Tato metoda jednoduše zahodí všechen text, pokud na něj zrovna parser narazil
* @throws Exception
*/
private void ignoreText() throws Exception {
while(parser.getEventType() == parser.TEXT) {
parser.next();
}
}
/**
* Tato metoda zkopíruje právě zpracovávaný XML element do řetězce
*
* @param xpp Reference na XmlPullParser, který bude použit při kopírování.
* @return String obsahující otevírající tag, obsah elementu, a jeho uzavírající tag
* @throws Exception Pokud XmlPullParser není ve stavu START_TAG a pokud se zjistí invalidita dokumentu
*/
private String copyXMLElement(XmlPullParser xpp) throws Exception {
StringBuilder sb = new StringBuilder("");
// Pokud nejsem na START_TAG, vyhodim vyjimku
if(xpp.getEventType() != xpp.START_TAG) throw new Exception("xpp is not on START_TAG");
//jsem START TAG, zkopiruju se na vystup a pak zkopiruju svuj obsah
sb.append(xpp.getText());
// nejsem-li prázdný, zkopíruju svůj obsah
if(!xpp.isEmptyElementTag()) {
// pokud je pristim eventem TEXT, zkopiruju text
int event = xpp.next();
if(event == xpp.TEXT) {
sb.append(xpp.getText());
event = xpp.next();
}
//dokud neni pristi element END_TAG, kopiruju tagy a přidávám je do StringBufferu.
while(event != xpp.END_TAG) {
// je-li to text, zkopiruju
if(event == xpp.TEXT) {
// zkopiruju text a posunu rafičku
sb.append(xpp.getText());
event = xpp.next();
continue;
}
if(event == xpp.START_TAG) {
// pripojim zkopirovany vnoreny element
sb.append(copyXMLElement(xpp));
// rafička je teď zase buď na TEXT, START TAG nebo END TAG
// pokud je na END TAG, je to na mě a je konec
event = xpp.getEventType();
continue;
}
}
// ted je na rade koncovy tag. jediny problem je v tom, ze
// kdyz dam gettext, bude tam s lomitkem na druhe strane..
sb.append(xpp.getText());
}
// byl-li jsem na empty tagu, rafička bude hned ted na END_TAG
else {
xpp.next();
}
// ted jsem na END_TAG, takze posunu parser
xpp.next();
return sb.toString();
}
/**
* Metoda vrátí objektovou reprezentaci SVGWB element <new> a posune parser za jeho koncový tag
* @return Instance objektu SVGWBNewElement
* @throws Exception Pokud není dokument validní, nebo pokud nezačínám na začátku tagu <new>
*/
private SVGWBNewElement parseNewElement() throws Exception{
String id = parser.getAttributeValue(null, "id");
String indexStr = parser.getAttributeValue(null, "index");
float index = Float.parseFloat(indexStr);
String versionStr = parser.getAttributeValue(null, "version");
String parent = parser.getAttributeValue(null, "parent");
SVGWBNewElement el = new SVGWBNewElement(id, index);
if(versionStr != null)
el.setVersion(Integer.parseInt(versionStr));
if(parent != null)
el.setParent(parent);
parser.next();
// ted jsem za zacatecnim tagem <new>. nasleduje bud TEXT, nebo START TAG..
ignoreText();
//ted musi byt START.
if(parser.getEventType() != parser.START_TAG) throw new Exception("invalid");
// zacal vnitrni tag. ten musi byt presne jeden, takze vezmu vsecko co je
// na tehle urovni.
String svgContent = copyXMLElement(parser);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new ByteArrayInputStream(svgContent.getBytes()));
el.setSVGContent((Element)doc.getDocumentElement().cloneNode(true));
//ted jsme za koncovym tagem SVG obsahu. bud je TEXT, ktery zahodim, anebo je END_TAG
ignoreText();
//ted musi prijit end tag tagu <new/>
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
// jsem na konci tagu new. zahodim ho.
parser.next();
return el;
}
/**
* Metoda vrátí objektovou reprezentaci elementu editace atributu a posune parser za jeho koncový tag
* @return Instance objektu AttributeElementEdit
* @throws Exception Pokud není dokument validní, nebo pokud nezačínám na příslušném otevírajícím tagu
*/
private SVGWBConfigureElement.AttributeElementEdit parseAttributeElementEdit() throws Exception {
//jsem na startu elementu attribute.
String name = parser.getAttributeValue(null, "name");
String fromStr = parser.getAttributeValue(null, "from");
String toStr = parser.getAttributeValue(null, "to");
//obsah elementu: je-li prázdný, content nastavím na prázdný řetězec
String content;
if(parser.isEmptyElementTag()) {
content = "";
//posunu parser na pozici END_TAG:
parser.next();
}
else {
//element není degenerovaný
int event = parser.next();
StringBuilder sb = new StringBuilder("");
while(event == parser.TEXT) {
sb.append(parser.getText());
event = parser.next();
}
content = sb.toString();
//ted jsem na pozici END_TAG, nebo START TAG. Pokud je START TAG, je to invalidní
if(event == parser.START_TAG) throw new Exception("invalid - tag v elementu attribute");
//ted jsem na pozici END_TAG
}
// posunu parser za END_TAG a nastavim hodnoty do objektu
parser.next();
SVGWBConfigureElement.AttributeElementEdit el = new SVGWBConfigureElement.AttributeElementEdit(name, content);
if(fromStr != null) {
int from = Integer.parseInt(fromStr);
el.setFrom(from);
}
if(toStr != null) {
int to = Integer.parseInt(toStr);
el.setTo(to);
}
return el;
}
/**
* Metoda vrátí objektovou reprezentaci elementu editace obsahu a posune parser za jeho koncový tag
* @return Instance objektu ContentElementEdit
* @throws Exception Pokud není dokument validní, nebo pokud nezačínám na příslušném otevírajícím tagu
*/
private SVGWBConfigureElement.ContentElementEdit parseContentElementEdit() throws Exception {
//content nema zadne atributy, zajima nas jen obsah..
String content;
if(parser.isEmptyElementTag()) {
content = "";
parser.next();
}
else {
StringBuilder sb = new StringBuilder("");
int event = parser.next();
while(event == parser.TEXT) {
sb.append(parser.getText());
event = parser.next();
}
content = sb.toString();
if(event == parser.START_TAG) throw new Exception("invalid - another element found inside a content element");
}
parser.next();
SVGWBConfigureElement.ContentElementEdit el = new SVGWBConfigureElement.ContentElementEdit(content);
return el;
}
/**
* Metoda vrátí objektovou reprezentaci elementu editace rodiče a posune parser za jeho koncový tag
* @return Instance objektu ParentElementEdit
* @throws Exception Pokud není dokument validní, nebo pokud nezačínám na příslušném otevírajícím tagu
*/
private SVGWBConfigureElement.ParentElementEdit parseParentElementEdit() throws Exception {
//parent nema zadne atributy, zajima nas jen obsah..
String content;
if(parser.isEmptyElementTag()) {
content = "";
parser.next();
}
else {
StringBuilder sb = new StringBuilder("");
int event = parser.next();
while(event == parser.TEXT) {
sb.append(parser.getText());
event = parser.next();
}
content = sb.toString();
if(event == parser.START_TAG) throw new Exception("invalid - another element found inside a parent element");
}
parser.next();
SVGWBConfigureElement.ParentElementEdit el = new SVGWBConfigureElement.ParentElementEdit(content);
return el;
}
/**
* Metoda vrátí objektovou reprezentaci SVGWB element <configure> a posune parser za jeho koncový tag
* @return Instance objektu SVGWBConfigureElement
* @throws Exception Pokud není dokument validní, nebo pokud nezačínám na začátku tagu <configure>
*/
private SVGWBConfigureElement parseConfigureElement() throws Exception {
String target = parser.getAttributeValue(null, "target");
String verStr = parser.getAttributeValue(null, "version");
int version = Integer.parseInt(verStr);
SVGWBConfigureElement el = new SVGWBConfigureElement(target, version);
// jsem na zacatku configure tagu. pokud je prázdný, přejdu za jeho konec a vrátím ho.
if(parser.isEmptyElementTag()) {
parser.next(); // posunul jsem se na END TAG configure
parser.next(); // posunul jsem se za tag configure
return el;
}
//element configure tedy není prázdný (ve smyslu nepárového tagu). Text mohu zahodit, není potřeba
parser.next();
ignoreText();
//teď bude následovat buď END_TAG, anebo START TAG některé z editací.
//pokud bude START TAG, editaci zpracuji a zahodím text za ní.
while(parser.getEventType() != parser.END_TAG) {
//teď musím být na START_TAGu.
// jsou tři typu elementů, na které zde mohu narazit. attribute, parent nebo content
String name = parser.getName();
if(name.equals("attribute")) {
el.addEdit(parseAttributeElementEdit());
}
if(name.equals("parent")) {
el.addEdit(parseParentElementEdit());
}
if(name.equals("content")) {
el.addEdit(parseContentElementEdit());
}
ignoreText();
}
//ted jsem na konci tagu configure. prejdu za nej a vrátím vytvořený objekt.
parser.next();
return el;
}
private SVGWBRemoveElement parseRemoveElement() throws Exception {
String target = parser.getAttributeValue(null, "target");
//remove element musí být prázdný, tj, další musí být end_tag.
parser.next();
ignoreText();
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
//posunu za konec remove elementu
parser.next();
return new SVGWBRemoveElement(target);
}
private SVGWBMoveElement parseMoveElement() throws Exception {
String target = parser.getAttributeValue(null, "target");
String diStr = parser.getAttributeValue(null, "di");
float di = Float.parseFloat(diStr);
parser.next();
ignoreText();
//move element musí být prázdný, tj, další musí být end_tag.
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
// posunu parser za konec elementu
parser.next();
return new SVGWBMoveElement(target, di);
}
private SVGWBProtocolElement.EmptyProtocolElement parseEmptyProtocolElement(
SVGWBProtocolElement.EmptyProtocolElement.Type type) throws Exception {
//tento element je prazdny. jsem na start tagu
parser.next();
ignoreText();
//ted jsem urcite na end_tagu. kdyz ne, je to spatne
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid - empty element is not empty");
//posunu se za konec
parser.next();
return new SVGWBProtocolElement.EmptyProtocolElement(type);
}
private SVGWBProtocolElement.ProtocolAbortNegotiationElement parseAbortNegotiationElement() throws Exception {
// start tagu abort negotiation - zadne atributy, jen podelementy, nepovinne
parser.next();
ignoreText();
// ted muzu byt bud na konci anebo na nejakem z duvodu.
SVGWBProtocolElement.ProtocolAbortNegotiationElement el = new SVGWBProtocolElement.ProtocolAbortNegotiationElement();
if(parser.getEventType() == parser.START_TAG) {
if(parser.getName().equals("supported-features")) {
// jsem na zacatku tagu supported features. ten nema atributy a obsahuje
// jen tagy feature
el.setFeaturesUnsupported(true);
parser.next();
ignoreText();
while(parser.getEventType() == parser.START_TAG) {
// jsem na zacatku tagu feature
if(!parser.getName().equals("feature")) throw new Exception("invalid");
parser.next();
//ted nasleduje nazev featury
if(parser.getEventType() != parser.TEXT) throw new Exception("invalid");
String text = parser.getText().trim();
if(text.isEmpty()) throw new Exception("invalid");
el.addFeature(text);
//konec tagu feature
if(parser.next() != parser.END_TAG) throw new Exception("invalid");
//posunu za konec
parser.next();
//preskocim text k dalsimu elementu feature nebo na konec tagu supported features
ignoreText();
}
// jsem na end_tagu. posunu se za nej
parser.next();
}
else if(parser.getName().equals("peer-already-in-session")) {
el.setAlreadyLogged(true);
// jsem na zacatku elementu
parser.next();
if(parser.getEventType() == parser.TEXT) {
String text = parser.getText().trim();
// muze obsahovat session id. kdyz ho neobsahuje, nic nedelam
if(!text.isEmpty())
el.setSessionID(text);
parser.next();
}
// ted musim byt na konci elementu
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
//posunu za konec
parser.next();
}
else throw new Exception("invalid - unknown reason-tag");
}
// jsem za tagem s duvodama
ignoreText();
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
parser.next();
return el;
}
private SVGWBProtocolElement.ProtocolDocumentEndElement parseDocumentEndElement() throws Exception {
// document end musi obsahovat element last-wb. sam nema zadne atributy, ale last-wb
// musi mit atributy sender a hash.
parser.next();
ignoreText();
if(parser.getEventType() != parser.START_TAG) throw new Exception("invalid");
if(!parser.getName().equals("last-wb")) throw new Exception("invalid");
String sender = parser.getAttributeValue(null, "sender");
String hash = parser.getAttributeValue(null, "hash");
if(sender == null || sender.isEmpty() || hash == null || hash.isEmpty()) throw new Exception("invalid");
SVGWBProtocolElement.ProtocolDocumentEndElement el = new SVGWBProtocolElement.ProtocolDocumentEndElement(sender, hash);
parser.next();
ignoreText();
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
//posunu se za konec
parser.next();
return el;
}
private SVGWBProtocolElement.ProtocolElementWithFeatures parseElementWithFeatures(
SVGWBProtocolElement.ProtocolElementWithFeatures.Type type) throws Exception {
// jsem na zacatku elementu s featurama. tyhle elementy nemaji zadne atributy,
// jen podelementy feature, jez maji textovy obsah, ktery nas zajima.
SVGWBProtocolElement.ProtocolElementWithFeatures el = new SVGWBProtocolElement.ProtocolElementWithFeatures(type);
//posunu za start
parser.next();
ignoreText();
while(parser.getEventType() == parser.START_TAG) {
if(!parser.getName().equals("feature")) throw new Exception("invalid");
// ted jsme na zacatku tagu feature.
parser.next();
if(parser.getEventType() != parser.TEXT) throw new Exception("invalid");
String text = parser.getText().trim();
if(text.isEmpty()) throw new Exception("invalid - empty feature");
el.addFeature(text);
parser.next();
if(parser.getEventType() != parser.END_TAG) throw new Exception("invalid");
// jsme na konci tagu, posunu se dal
parser.next();
ignoreText();
}
//jsem na konci tagu, posunu se dal
parser.next();
return el;
}
private SVGWBProtocolElement parseProtocolElement() throws Exception {
//element protocol nema zadne atributy.
//protocol ma prave jeden podelement urcujici typ zpravy.
parser.next();
ignoreText();
if(parser.getEventType() != parser.START_TAG) throw new Exception("invalid protocol element");
//jsem na zacatku tagu
String name = parser.getName();
SVGWBProtocolElement el;
if(name.equals("history-offer")) el = parseElementWithFeatures(SVGWBProtocolElement.ProtocolElementWithFeatures.Type.history_offer) ;
else if(name.equals("abort-negotiation")) el = parseAbortNegotiationElement();
else if(name.equals("invitation")) el = parseElementWithFeatures(SVGWBProtocolElement.ProtocolElementWithFeatures.Type.invitation);
else if(name.equals("connect-request")) el = parseEmptyProtocolElement(SVGWBProtocolElement.EmptyProtocolElement.Type.connect_request);
else if(name.equals("accept-invitation")) el = parseEmptyProtocolElement(SVGWBProtocolElement.EmptyProtocolElement.Type.accept_invitation) ;
else if(name.equals("accept-history")) el = parseEmptyProtocolElement(SVGWBProtocolElement.EmptyProtocolElement.Type.accept_history);
else if(name.equals("document-begin")) el = parseEmptyProtocolElement(SVGWBProtocolElement.EmptyProtocolElement.Type.document_begin);
else if(name.equals("document-end")) el = parseDocumentEndElement();
else throw new Exception("unknown protocol tag");
// ted jsem za koncem vnitrniho elementu
ignoreText();
//ted musi byt end tag
if(parser.getEventType() != parser.END_TAG) throw new Exception("more than one elements in protocol element");
//jdu za end tag
parser.next();
return el;
}
private SVGWBExtension parseRootElement() throws Exception {
String session = parser.getAttributeValue(null, "sesion");
String hash = parser.getAttributeValue(null, "hash");
SVGWBExtension ext = new SVGWBExtension(session, hash);
parser.next();
//ted jsem za zacatkem korenoveho elementu.
//dokud zacinaji nove elementy, tak je zpracovavam.
ignoreText();
while(parser.getEventType() == parser.START_TAG) {
String name = parser.getName();
if(name.equals("protocol"))
ext.addElement(parseProtocolElement());
else if(name.equals("move"))
ext.addElement(parseMoveElement());
else if(name.equals("remove"))
ext.addElement(parseRemoveElement());
else if(name.equals("new"))
ext.addElement(parseNewElement());
else if(name.equals("configure"))
ext.addElement(parseConfigureElement());
else throw new Exception("unknown element");
//přeskočím text k dalšímu elementu
ignoreText();
}
// ted je end_tag korenoveho elementu
//ted uz jsem na konci dokumentu
//if(parser.getEventType() != parser.END_DOCUMENT) throw new Exception("eof expected");
return ext;
}
@Override
public PacketExtension parseExtension(XmlPullParser xppp) throws Exception {
parser = xppp;
if(parser.getEventType() != parser.START_TAG) throw new Exception();
//zpracovat prvni element. musi to byt wb. ale mam to validovat? to prece ne
// spoleham na to, ze je to validni..
PacketExtension ext = parseRootElement();
return ext;
}
}