/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.html;


import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import woolpack.dom.DomConstants;
import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;
import woolpack.dom.NodeLoop;

/**
 * 定数・静的メソッドの集まり。
 * @author nakamura
 *
 */
public class HtmlConstants {
	private static final Pattern SPACE_PATTERN = Pattern.compile("[ \\t\\r\\n]{2,}");
	
	/**
	 * 指定された DOM ノードを削除し、さらにその後ろにあるテキストノードを全て削除する{@link DomExpression}。
	 * ラジオボタン・チェックボックスを削除するために使用する。
	 * {@link DomExpression#interpret(DomContext)}では引数または{@link DomContext#getNode()}が null の場合に{@link NullPointerException}を投げる。
	 */
	public static final DomExpression REMOVE_THIS_AND_TEXTS = new DomExpression(){
		public void interpret(final DomContext context) {
			removeThisAndText(context.getNode());
		}
	};
	
	/**
	 * ドキュメントノードに対して{@link Node#normalize()}を呼び出す{@link DomExpression}。
	 * {@link DomExpression#interpret(DomContext)}では引数または{@link DomContext#getNode()}が null の場合に{@link NullPointerException}を投げる。
	 */
	public static final DomExpression NORMALIZE = new DomExpression(){
		public void interpret(final DomContext context) {
			DomConstants.getDocumentNode(context.getNode()).normalize();
		}
	};
	
	/**
	 * エレメント名を大文字に、属性名を小文字に変換する{@link DomExpression}。
	 * {@link DomExpression#interpret(DomContext)}では引数または{@link DomContext#getNode()}が null の場合に{@link NullPointerException}を投げる。
	 */
	public static final DomExpression NORMALIZE_CASE = new NodeLoop(){
		@Override 
		public void interpret(final DomContext context) {
			super.interpret(context);
			if(context.getNode().getNodeType() == Node.ELEMENT_NODE){
				final Element e = (Element)context.getNode();
				final Element newE;
				{
					final String s = e.getNodeName().toUpperCase();
					if(s.equals(e.getNodeName())){
						newE = e;
					}else{
						newE = DomConstants.getDocumentNode(e).createElement(s);
					}
				}
				
				{
					final NamedNodeMap attrs = e.getAttributes();
					final Attr[] attrArray = new Attr[attrs.getLength()];
					for(int i=0; i<attrArray.length; i++){
						attrArray[i] = (Attr)attrs.item(i);
					}
					for(int i=0; i<attrArray.length; i++){
						final Attr attr = attrArray[i];
						final String s = attr.getName().toLowerCase();
						if(e != newE || !s.equals(attr.getName())){
							if(e == newE){
								newE.removeAttributeNode(attr);
							}
							newE.setAttribute(s, attr.getNodeValue());
						}
					}
				}
				if(e != newE){
					{
						Node child = null;
						while((child = e.getFirstChild()) != null){
							newE.appendChild(child);
						}
					}
					e.getParentNode().replaceChild(newE, e);
				}
			}
		}
	};

	/**
	 * SCRIPT ノード以外のコメントノードを削除する{@link DomExpression}。
	 * {@link DomExpression#interpret(DomContext)}では引数または{@link DomContext#getNode()}が null の場合に{@link NullPointerException}を投げる。
	 */
	public static final DomExpression REMOVE_COMMENT = new NodeLoop(){
		@Override 
		public void interpret(final DomContext context) {
			super.interpret(context);
			if(context.getNode().getNodeType() == Node.COMMENT_NODE){
				if(!context.getNode().getParentNode().getNodeName().equals("SCRIPT")){
					DomConstants.removeThis(context.getNode());
				}
			}
		}
	};
	
	/**
	 * テキストノードの空白文字を圧縮する{@link DomExpression}。
	 * {@link DomExpression#interpret(DomContext)}では引数または{@link DomContext#getNode()}が null の場合に{@link NullPointerException}を投げる。
	 */
	public static final DomExpression COMPRESS_SPACE = new NodeLoop(){
		@Override 
		public void interpret(final DomContext context) {
			super.interpret(context);
			if(context.getNode().getNodeType() == Node.TEXT_NODE){
				final Text t = (Text)context.getNode();
				final Matcher m = SPACE_PATTERN.matcher(t.getWholeText());
				t.replaceWholeText(m.replaceAll(" "));
			}
		}
	};

	private HtmlConstants(){}// カバレージがここを通過してはいけない
	
	/**
	 * 指定された DOM ノードを削除し、さらにその後ろにあるテキストノードを全て削除する。
	 * ラジオボタン・チェックボックスを削除するために使用する。
	 * @param node 削除対象の基点ノード。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static void removeThisAndText(final Node node){
		Node nextNode = node.getNextSibling();
		while(nextNode != null && nextNode.getNodeType() == Node.TEXT_NODE){
			DomConstants.removeThis(nextNode);
			nextNode = node.getNextSibling();
		}
		DomConstants.removeThis(node);
	}
}
