/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012 gnosygnu@gmail.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package gplx.xowa.parsers.htmls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
import gplx.core.primitives.*;
import gplx.xowa.parsers.xndes.*;	// for brys: <nowiki>, <noinclude>, <includeonly>, <onlyinclude>
public class Mwh_atr_parser {	// REF.MW:Sanitizer.php|decodeTagAttributes;MW_ATTRIBS_REGEX
	private static final byte Area__invalid = 0, Area__atr_limbo = 1, Area__key = 2, Area__eql_limbo = 3, Area__val_limbo = 4, Area__val_quote = 5, Area__val_naked = 6;
	private final    Hash_adp_bry repeated_atrs_hash = Hash_adp_bry.ci_a7();		// ASCII:xnde_atrs
	private final    Mwh_atr_mgr atr_mgr = new Mwh_atr_mgr(16);
	private final    Bry_bfr key_bfr = Bry_bfr_.New(), val_bfr = Bry_bfr_.New();
	private byte area = Area__atr_limbo;
	private int atr_bgn = -1, key_bgn = -1, key_end = -1, eql_pos = -1, val_bgn = -1, val_end = -1;
	private byte qte_byte = Byte_ascii.Null;
	private boolean key_bfr_on = false, val_bfr_on = false, ws_is_before_val = false, qte_closed = false;
	private int nde_uid, nde_tid;
	public Bry_obj_ref Bry_obj() {return bry_ref;} private final    Bry_obj_ref bry_ref = Bry_obj_ref.New_empty();
	public int Nde_end_tid() {return nde_end_tid;} private int nde_end_tid;
	public int Parse(Mwh_atr_wkr wkr, int nde_uid, int nde_tid, byte[] src, int src_bgn, int src_end) {
		this.nde_uid = nde_uid; this.nde_tid = nde_tid;
		this.nde_end_tid = Mwh_doc_parser.Nde_end_tid__invalid;
		this.atr_bgn = -1;
		area = Area__atr_limbo;
		boolean prv_is_ws = false;
		int pos = src_bgn;
		boolean loop = true;
		while (loop) {
			if (pos >= src_end) {
				switch (area) {
					case Area__key:			// EX: "a"
					case Area__eql_limbo:	// EX: "a "
					case Area__val_naked:	// EX: "a=b"
						break;				// valid atr
					case Area__val_quote:	// EX: "a='b'"
						if (qte_closed)
							Make(src, src_end);
						else {				// dangling; EX: "a='b c=d"
							int reset_pos = Bry_find_.Find_fwd(src, Byte_ascii.Space, val_bgn, src_end);	// try to find 1st space within quote; EX:"a='b c=d" should try to reset at c=d
							boolean reset_found = reset_pos != Bry_find_.Not_found;
							area = Area__invalid; val_end = reset_found ? reset_pos : src_end;
							Make(src, val_end);	// create invalid atr
							if (reset_found) {	// space found; resume from text after space; EX: "a='b c=d"; PAGE:en.w:Aubervilliers DATE:2014-06-25
								pos = Bry_find_.Find_fwd_while_not_ws(src, reset_pos, src_end);	// skip ws
								atr_bgn = -1;
								area = Area__atr_limbo;
								continue;
							}
						}
						break;
					case Area__invalid: case Area__atr_limbo:
					case Area__val_limbo:
						area = Area__invalid;
						break;
				}
				if (atr_bgn != -1) {
					val_end = src_end;
					Make(src, val_end);
				}
				break;
			}
			byte b = src[pos];
			switch (area) {
				case Area__invalid:
					switch (b) {
						// ws -> end invalid area
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:
							Make(src, pos);
							area = Area__atr_limbo;
							break;
						// rest -> continue eating up invalid chars
						default:
							break;
					}
					break;
				case Area__atr_limbo:	// 1st area after (a) node_name, (b) attribute, (c) invalid_area
					switch (b) {
						// ws -> ignore; skip any ws in atr_limbo; note that once a non-ws char is encountered, it will immediately go into another area
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:								
							if (atr_bgn == -1) atr_bgn = pos;	// NOTE: atr_bgn == -1 needed for multiple spaces; ALSO: cannot move above switch b/c of <nowiki>
							break;
						// attribFirst -> enter Area__key; REF.MW: $attribFirst = '[:A-Z_a-z0-9]';
						case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
						case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
						case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
						case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
						case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
						case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
						case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
						case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
						case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
						case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
						case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
						case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
						case Byte_ascii.Colon: case Byte_ascii.Underline:
							area = Area__key;
							if (atr_bgn == -1) atr_bgn = pos;	// NOTE: atr_bgn == -1 needed b/c of spaces
							key_bgn = pos;
							break;
						// angle_bgn -> check for <nowiki>
						case Byte_ascii.Angle_bgn:		// handle "<nowiki>"
							int gt_pos = Xnde_find_gt(src, pos, src_end);
							if (gt_pos == Bry_find_.Not_found) {
								area = Area__invalid; if (atr_bgn == -1) atr_bgn = pos;
							}
							else
								pos = gt_pos;	// position after ">"; note that there is ++pos below and loop will continue at gt_pos + 1 (next character after)								
							break;
						// rest -> invalid
						default:				// quote and other non-valid key characters are invalid until next space; EX: "<span 'key_cannot_be_quoted' id='123'"
							area = Area__invalid; if (atr_bgn == -1) atr_bgn = pos;
							break;
					}
					break;
				case Area__key:
					switch (b) {
						// alphanum -> valid key chars; REF.MW: $attrib = '[:A-Z_a-z-.0-9]';
						case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
						case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
						case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
						case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
						case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
						case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
						case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
						case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
						case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
						case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
						case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
						case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
						case Byte_ascii.Colon: case Byte_ascii.Underline: case Byte_ascii.Dash: case Byte_ascii.Dot:
							if (key_bfr_on) key_bfr.Add_byte(b);
							break;
						// ws -> end key
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:
							area = Area__eql_limbo;
							key_end = pos;
							break;
						// eq -> end key; go to Area_val_limbo
						case Byte_ascii.Eq:
							area = Area__val_limbo;
							key_end = eql_pos = pos;
							break;	
						// angle_bgn -> check for <nowiki>
						case Byte_ascii.Angle_bgn:
							int gt_pos = Xnde_find_gt(src, pos, src_end);
							if (gt_pos == Bry_find_.Not_found)	// "<" should not be in key; EX: "ke<y"
								area = Area__invalid;
							else {
								if (!key_bfr_on) {key_bfr.Add_mid(src, key_bgn, pos); key_bfr_on = true;}
								pos = gt_pos;	// note that there is ++pos below and loop will continue at gt_pos + 1 (next character after)
							}
							break;
						// rest -> enter invalid
						default:
							area = Area__invalid;
							break;
					}
					break;
				case Area__eql_limbo:
					switch (b) {
						// ws -> skip
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space: // skip ws
							break;
						// eq -> enter Area__val_limbo
						case Byte_ascii.Eq:
							eql_pos = pos;
							area = Area__val_limbo;
							break;
						// attribFirst -> enter Area__key; REF.MW: $attribFirst = '[:A-Z_a-z0-9]';
						case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
						case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
						case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
						case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
						case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
						case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
						case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
						case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
						case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
						case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
						case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
						case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
						case Byte_ascii.Colon: case Byte_ascii.Underline:
							Make(src, pos);
							area = Area__key;
							atr_bgn = key_bgn = pos;
							break;
						// rest -> make atr and enter limbo
						default:
							area = Area__invalid;
							break;
					}
					break;
				case Area__val_limbo:
					switch (b) {
						// ws -> skip
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:
							ws_is_before_val = true;
							break;
						// quote -> enter Area_val_quote
						case Byte_ascii.Quote: case Byte_ascii.Apos:
							area = Area__val_quote; qte_byte = b; qte_closed = false;
							prv_is_ws = false;
							val_bgn = pos + 1;
							break;
						// alphanum -> enter Area_val_raw; REF.MW: [a-zA-Z0-9!#$%&()*,\\-.\\/:;<>?@[\\]^_`{|}~]+
						case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
						case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
						case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
						case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
						case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
						case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
						case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
						case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
						case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
						case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
						case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
						case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
						case Byte_ascii.Bang: case Byte_ascii.Hash: case Byte_ascii.Dollar: case Byte_ascii.Percent: case Byte_ascii.Amp:
						case Byte_ascii.Paren_bgn: case Byte_ascii.Paren_end: case Byte_ascii.Star: case Byte_ascii.Comma: case Byte_ascii.Dash: case Byte_ascii.Dot:
						case Byte_ascii.Backslash: case Byte_ascii.Slash: case Byte_ascii.Colon: case Byte_ascii.Semic:
						case Byte_ascii.Question: case Byte_ascii.At:
						case Byte_ascii.Brack_bgn: case Byte_ascii.Brack_end: case Byte_ascii.Pow: case Byte_ascii.Underline: case Byte_ascii.Tick:
						case Byte_ascii.Curly_bgn: case Byte_ascii.Curly_end: case Byte_ascii.Pipe: case Byte_ascii.Tilde:
							area = Area__val_naked;
							val_bgn = pos;
							break;
						// case Byte_ascii.Angle_end: NOTE: valid in MW; making invalid now until finding counter-example
						// angle_bgn -> check for <nowiki>
						case Byte_ascii.Angle_bgn:
							int gt_pos = Xnde_find_gt(src, pos, src_end);
							if (gt_pos == Bry_find_.Not_found)
								area = Area__invalid;	// NOTE: valid in MW; making invalid now until finding counter-example
							else
								pos = gt_pos;	// note that there is ++pos below and loop will continue at gt_pos + 1 (next character after)
							break;
						// rest -> ignore
						default:
							area = Area__invalid;
							break;
					}
					break;
				case Area__val_quote: {	// EX: "'val' " in "key = 'val'"; REF.MW: \"([^<\"]*)\"
					switch (b) {
						// quote: check if same as opening quote
						case Byte_ascii.Quote: case Byte_ascii.Apos:
							if (qte_closed)
								area = Area__invalid;
							else {
								if (qte_byte == b) {	// quote closes val
									qte_closed = true;
									val_end = pos;
								}
								else {					// quote is just char; EX: title="1 o'clock" or title='The "C" way'
									prv_is_ws = false; if (val_bfr_on) val_bfr.Add_byte(b);		// INLINE: add char
								}
							}
							break;
						// ws -> convert all ws to \s; only allow 1 ws at any point in time
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:	// REF.MW:Sanitizer.php|decodeTagAttributes $value = preg_replace( '/[\t\r\n ]+/', ' ', $value );
							if (qte_closed) {
								Make(src, pos);	// NOTE: set atr_end *after* quote
								if (atr_bgn == -1) atr_bgn = pos;	// NOTE: process ws just like Area__atr_limbo
							}
							else {
								if (!val_bfr_on) {val_bfr.Add_mid(src, val_bgn, pos); val_bfr_on = true;}	// INLINE: val_bfr.init
								if (prv_is_ws) {}	// noop; only allow one ws at a time; EX: "a  b" -> "a b"; "a\n\nb" -> "a b"
								else {
									prv_is_ws = true; val_bfr.Add_byte(Byte_ascii.Space);									
								}
							}
							break;
						// angle_bgn -> check for <nowiki>; EX: <span title='ab<nowiki>c</nowiki>de'>
						case Byte_ascii.Angle_bgn:
							int gt_pos = Xnde_find_gt(src, pos, src_end);
							if (gt_pos == Bry_find_.Not_found) {	
								// area = Area__invalid;	// "<" inside quote is invalid; EX: <span title='a<b'>c</span>
								if (val_bfr_on) val_bfr.Add_byte(b);		// INLINE: add char
							}
							else {
								if (qte_closed) {}
								else {
									if (!val_bfr_on) {val_bfr.Add_mid(src, val_bgn, pos); val_bfr_on = true;}	// INLINE: val_bfr.init
								}
								pos = gt_pos;	// note that there is ++pos below and loop will continue at gt_pos + 1 (next character after)
							}
							prv_is_ws = false;
							break;
						// rest -> add to val
						default:
							if (qte_closed)
								area = Area__invalid;
							else {
								prv_is_ws = false; if (val_bfr_on) val_bfr.Add_byte(b);		// INLINE: add char
							}
							break;
					}
					break;
				}
				case Area__val_naked:	// no quotes; EX:a=bcd; REF.MW:([a-zA-Z0-9!#$%&()*,\\-.\\/:;<>?@[\\]^_`{|}~]+)
					switch (b) {
						// alphanum -> continue reading
						case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
						case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
						case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
						case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
						case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
						case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
						case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
						case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
						case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
						case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
						case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
						case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
						case Byte_ascii.Bang: case Byte_ascii.Hash: case Byte_ascii.Dollar: case Byte_ascii.Percent: case Byte_ascii.Amp:
						case Byte_ascii.Paren_bgn: case Byte_ascii.Paren_end: case Byte_ascii.Star: case Byte_ascii.Comma: case Byte_ascii.Dash: case Byte_ascii.Dot:
						case Byte_ascii.Backslash: case Byte_ascii.Slash: case Byte_ascii.Colon: case Byte_ascii.Semic:
						case Byte_ascii.Question: case Byte_ascii.At:
						case Byte_ascii.Brack_bgn: case Byte_ascii.Brack_end: case Byte_ascii.Pow: case Byte_ascii.Underline: case Byte_ascii.Tick:
						case Byte_ascii.Curly_bgn: case Byte_ascii.Curly_end: case Byte_ascii.Pipe: case Byte_ascii.Tilde:
							if (val_bfr_on) val_bfr.Add_byte(b);		// INLINE: add char
							break;
						// case Byte_ascii.Angle_end: NOTE: valid in MW; making invalid now until finding counter-example
						// angle_bgn -> check for <nowiki>; EX: a=b<nowiki>c</nowiki>d
						case Byte_ascii.Angle_bgn:
							int gt_pos = Xnde_find_gt(src, pos, src_end);
							if (gt_pos == Bry_find_.Not_found) {
								area = Area__invalid;	// NOTE: valid in MW; making invalid now until finding counter-example
							}
							else {
								if (!val_bfr_on) {val_bfr.Add_mid(src, val_bgn, pos); val_bfr_on = true;}	// INLINE: val_bfr.init
								pos = gt_pos;	// note that there is ++pos below and loop will continue at gt_pos + 1 (next character after)								
							}
							break;
						// ws -> src_end atr
						case Byte_ascii.Tab: case Byte_ascii.Nl: case Byte_ascii.Cr: case Byte_ascii.Space:
							val_end = pos;
							Make(src, pos);
							break;
						case Byte_ascii.Eq:	// EX:"a= b=c" or "a=b=c"; PAGE:en.w:2013_in_American_television
							if (ws_is_before_val) {		// "a= b=c"; discard 1st and resume at 2nd
								int old_val_bgn = val_bgn;
								area = Area__invalid; Make(src, val_bgn);	// invalidate cur atr; EX:"a="
								atr_bgn = key_bgn = old_val_bgn;	// reset atr / key to new atr; EX: "b"
								key_end = pos;
								area = Area__val_limbo;				// set area to val_bgn (basically, put after =)
							}
							else									// "a=b=c"; discard all
								area = Area__invalid;
							break;
						default:
							area = Area__invalid;
							break;
					}
					break;			
			}
			++pos;
		}

		// iterate atrs and notify
		int len = atr_mgr.Len();
		int[] data_ary = atr_mgr.Data_ary();
		byte[][] text_ary = atr_mgr.Text_ary();
		for (int j = 0; j < len; ++j) {
			int itm_idx = j * Mwh_atr_mgr.Idx__mult;
			byte[] key_bry = text_ary[j * Mwh_atr_mgr.Text__mult];
			byte[] val_bry_manual = null;
			int atr_utl = data_ary[itm_idx + Mwh_atr_mgr.Idx_atr_utl];
			boolean atr_valid = (atr_utl & Mwh_atr_itm_.Mask__valid) == Mwh_atr_itm_.Mask__valid;
			boolean repeated = (atr_utl & Mwh_atr_itm_.Mask__repeated) == Mwh_atr_itm_.Mask__repeated;
			boolean key_exists = (atr_utl & Mwh_atr_itm_.Mask__key_exists) == Mwh_atr_itm_.Mask__key_exists;
			boolean val_made = (atr_utl & Mwh_atr_itm_.Mask__val_made) == Mwh_atr_itm_.Mask__val_made;
			if (val_made)
				val_bry_manual = text_ary[(j * Mwh_atr_mgr.Text__mult) + 1];
			wkr.On_atr_each(this, src, nde_tid, atr_valid, repeated, key_exists, key_bry, val_bry_manual, data_ary, itm_idx);
		}
		atr_mgr.Clear();
		repeated_atrs_hash.Clear();

		return pos;
	}
	private void Make(byte[] src, int atr_end) {
		// calc final values for atr
		boolean key_exists = false;
		byte[] key_bry = null, val_bry = null;
		boolean atr_valid = true;
		if (area == Area__invalid) {
			atr_valid = false;
			key_bry = Bry_.Empty;
			key_bfr.Clear();
			if (val_bgn == -1) val_bgn = atr_bgn;
			val_bfr.Clear();
		}
		else {
			if (key_bgn != -1 && val_bgn != -1)				// key && val exists; EX: "<input id='123'>"
				key_exists = true;
			else {											// not a pair; EX: "<input checked>"
				if (key_end == -1) key_end = val_end;		// NOTE: key_end == -1 when eos; EX: "a" would have key_bgn = 0; key_end = -1; val_end = 1 DATE:2014-07-03
				val_bgn = val_end = -1;
			}
			key_bry = key_bfr_on ? key_bfr.To_bry_and_clear() : Bry_.Mid(src, key_bgn, key_end);	// always make key_bry; needed for repeated_atrs as well as key_tid
			if (val_bfr_on) val_bry = val_bfr.To_bry_and_clear();
		}
		int qte_tid = Mwh_atr_itm_.Mask__qte__none;
		if (qte_byte != Byte_ascii.Null)
			qte_tid = qte_byte == Byte_ascii.Quote ? Mwh_atr_itm_.Mask__qte_qute : Mwh_atr_itm_.Mask__qte__apos;
		int atr_uid = atr_mgr.Add(nde_uid, nde_tid, atr_valid, false, key_exists, atr_bgn, atr_end, key_bgn, key_end, key_bry, eql_pos, qte_tid, val_bgn, val_end, val_bry);

		// handle repeated atrs
		if (atr_valid) {
			int repeated_uid = repeated_atrs_hash.Get_as_int_or(key_bry, -1);
			if (repeated_uid != -1) {
				repeated_atrs_hash.Del(key_bry);
				atr_mgr.Set_repeated(repeated_uid);
			}
			repeated_atrs_hash.Add_bry_int(key_bry, atr_uid);
		}
		
		// reset temp variables
		area = Area__atr_limbo; qte_byte = Byte_ascii.Null;
		atr_bgn = key_bgn = val_bgn = key_end = val_end = eql_pos = -1;
		key_bfr_on = val_bfr_on = ws_is_before_val = qte_closed = false;
	}
	public int Xnde_find_gt_find(byte[] src, int pos, int end) {
		bry_ref.Val_(Bry_.Empty);
		byte b = src[pos];
		if (b == Byte_ascii.Slash && pos + 1 < end) {	// if </ move pos to after /
			++pos;
			b = src[pos];
		}
		int gt_pos = Bry_find_.Find_fwd(src, Byte_ascii.Gt, pos, end); if (gt_pos == Bry_find_.Not_found) return Bry_find_.Not_found;
		byte[] bry = (byte[])xnde_hash.Get_by_mid(src, pos, gt_pos); if (bry == null) return Bry_find_.Not_found;
		bry_ref.Val_(bry);
		return bry.length + pos;
	}
	private int Xnde_find_gt(byte[] src, int lt_pos, int end) {
		int pos = lt_pos + 1; if (pos == end) return Bry_find_.Not_found;
		byte b = src[pos];
		if (b == Byte_ascii.Slash && pos + 1 < end) {
			++pos;
			b = src[pos];
		}
		int match_pos = Xnde_find_gt_find(src, pos, end);
		if (match_pos == Bry_find_.Not_found) {return Bry_find_.Not_found;}
		boolean slash_found = false;
		for (int i = match_pos; i < end; i++) {
			b = src[i];
			switch (b) {
				case Byte_ascii.Gt:			return i;
				case Byte_ascii.Space: case Byte_ascii.Nl: case Byte_ascii.Tab: // skip any ws
					break;
				case Byte_ascii.Slash:
					if (slash_found)		{return Bry_find_.Not_found;}	// only allow one slash
					else					slash_found = true;
					break;
				default:
					return Bry_find_.Not_found;
			}
		}
		return Bry_find_.Not_found;
	}
	private static final    Hash_adp_bry xnde_hash = Hash_adp_bry.ci_a7()
	.Add_bry_bry(Xop_xnde_tag_.Tag__nowiki.Name_bry())
	.Add_bry_bry(Xop_xnde_tag_.Tag__noinclude.Name_bry())
	.Add_bry_bry(Xop_xnde_tag_.Tag__includeonly.Name_bry())
	.Add_bry_bry(Xop_xnde_tag_.Tag__onlyinclude.Name_bry())
	;
	public static final int Key_tid__unknown = -1;
}
