/*
* Copyright 2008-2011 the original author or authors.
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.nominanuda.web.htmlcomposer;
import java.util.Stack;
import java.util.function.Supplier;
import javax.xml.transform.sax.SAXResult;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import com.nominanuda.zen.common.Tuple2;
import com.nominanuda.zen.xml.ForwardingTransformerHandlerBase;
import com.nominanuda.zen.xml.SaxBuffer.SaxBit;
import com.nominanuda.zen.xml.SwallowingTransformerHandlerBase;
public abstract class DomManipulationHandler extends ForwardingTransformerHandlerBase {
private int nestingLevel = 0;
private final DomManipulationStmt stmt;
private final JquerySelectorExpr jqSelector;
private final Stack<Tuple2<Integer, Supplier<Void>>> triggerStack = new Stack<Tuple2<Integer,Supplier<Void>>>();
protected final Stack<HtmlTag> parentElementStack = new Stack<HtmlTag>();
private ContentHandler liveContentHandler;
private final static ContentHandler devNull = new SwallowingTransformerHandlerBase();
public static DomManipulationHandler build(DomManipulationStmt stmt) {
switch (stmt.getOperation()) {
case html:
return new HtmlHandler(stmt);
case replaceWith:
return new ReplaceWithHandler(stmt);
case before:
return new BeforeHandler(stmt);
case after:
return new AfterHandler(stmt);
case prepend:
return new PrependHandler(stmt);
case append:
return new AppendHandler(stmt);
}
throw new IllegalStateException("unsupported operation:" + stmt.getOperation().name());
}
private DomManipulationHandler(DomManipulationStmt domManipulationStmt) {
stmt = domManipulationStmt;
jqSelector = new JquerySelectorExpr(stmt.getSelector());
}
// protected String getSelector() {
// return stmt.getSelector();
// }
//
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
nestingLevel++;
if(matches(localName, atts, parentElementStack)) {
onMatchedStartElement(uri, localName, qName, atts);
} else {
super.startElement(uri, localName, qName, atts);
}
parentElementStack.push(new HtmlTag(localName, atts));
}
protected abstract void onMatchedStartElement(String uri, String localName, String qName,
Attributes atts) throws SAXException;
private void onTriggerEndElement() {
Supplier<Void> f = triggerStack.pop().get1();
f.get();
}
protected void pushTriggerEndElement(Supplier<Void> trigger) {
triggerStack.push(new Tuple2<Integer, Supplier<Void>>(nestingLevel, trigger));
};
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if(! triggerStack.isEmpty() && triggerStack.peek().get0() == nestingLevel) {
onTriggerEndElement();
} else {
super.endElement(uri, localName, qName);
}
parentElementStack.pop();
nestingLevel--;
}
private boolean matches(String tag, Attributes atts, Stack<HtmlTag> parents) {
return jqSelector.matches(tag, atts, parents);
}
protected void turnOnOutput() {
if(liveContentHandler == null) {
liveContentHandler = getTarget();
}
setResult(new SAXResult(liveContentHandler));
}
protected void turnOffOutput() {
if(liveContentHandler == null) {
liveContentHandler = getTarget();
}
setResult(new SAXResult(devNull));
}
protected void streamFragment() throws SAXException {
ContentHandler ch = getTarget();
for(SaxBit b : stmt.getSaxBuffer().getBits()) {
b.send(ch);
}
}
private static class HtmlHandler extends DomManipulationHandler {
public HtmlHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
turnOffOutput();
pushTriggerEndElement(new Supplier<Void>() {
public Void get() {
try {
turnOnOutput();
getTarget().startElement(uri, localName, qName, atts);
streamFragment();
getTarget().endElement(uri, localName, qName);
return null;
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
});
}
}
private static class ReplaceWithHandler extends DomManipulationHandler {
public ReplaceWithHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
turnOffOutput();
pushTriggerEndElement(new Supplier<Void>() {
public Void get() {
try {
turnOnOutput();
streamFragment();
return null;
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
});
}
}
private static class BeforeHandler extends DomManipulationHandler {
public BeforeHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
streamFragment();
getTarget().startElement(uri, localName, qName, atts);
}
}
private static class PrependHandler extends DomManipulationHandler {
public PrependHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
getTarget().startElement(uri, localName, qName, atts);
streamFragment();
}
}
private static class AppendHandler extends DomManipulationHandler {
public AppendHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
getTarget().startElement(uri, localName, qName, atts);
pushTriggerEndElement(new Supplier<Void>() {
public Void get() {
try {
streamFragment();
getTarget().endElement(uri, localName, qName);
return null;
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
});
}
}
private static class AfterHandler extends DomManipulationHandler {
public AfterHandler(DomManipulationStmt domManipulationStmt) {
super(domManipulationStmt);
}
@Override
protected void onMatchedStartElement(final String uri, final String localName,
final String qName, final Attributes atts) throws SAXException {
getTarget().startElement(uri, localName, qName, atts);
pushTriggerEndElement(new Supplier<Void>() {
public Void get() {
try {
getTarget().endElement(uri, localName, qName);
streamFragment();
return null;
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
});
}
}
}