/*
 * 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.AbstractNodeLoop;
import woolpack.dom.DomConstants;
import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;

/**
 * 定数・静的メソッドの集まり。
 * 
 * @author nakamura
 * 
 */
public final 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 AbstractNodeLoop() {
		@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 AbstractNodeLoop() {
		@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 AbstractNodeLoop() {
		@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);
	}
}
