From 62c4eb45e157d502463d797c1353802ca8e1e307 Mon Sep 17 00:00:00 2001 From: sg481n Date: Fri, 25 Aug 2017 01:57:24 -0400 Subject: Update project structure for aaf/cadi Update project structure from com.att to org.onap and add distribution management and staging plugin. Issue-id: AAF-22 Change-Id: Idf2b591139e38921ad28782a51486714a05dee92 Signed-off-by: sg481n --- .../main/java/org/onap/aaf/cadi/wsse/Action.java | 37 ++ .../main/java/org/onap/aaf/cadi/wsse/Match.java | 130 +++++++ .../java/org/onap/aaf/cadi/wsse/WSSEParser.java | 86 +++++ .../main/java/org/onap/aaf/cadi/wsse/XEvent.java | 135 +++++++ .../main/java/org/onap/aaf/cadi/wsse/XReader.java | 416 +++++++++++++++++++++ 5 files changed, 804 insertions(+) create mode 100644 core/src/main/java/org/onap/aaf/cadi/wsse/Action.java create mode 100644 core/src/main/java/org/onap/aaf/cadi/wsse/Match.java create mode 100644 core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java create mode 100644 core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java create mode 100644 core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java (limited to 'core/src/main/java/org/onap/aaf/cadi/wsse') diff --git a/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java b/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java new file mode 100644 index 0000000..e95337f --- /dev/null +++ b/core/src/main/java/org/onap/aaf/cadi/wsse/Action.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package org.onap.aaf.cadi.wsse; + +/** + * Interface to specify an action deep within a parsing tree on a local object + * + * We use a Generic so as to be flexible on create what that object actually is. This is passed in at the + * root "parse" call of Match. Similar to a "Visitor" Pattern, this object is passed upon reaching the right + * point in a parse tree. + * + * + * @param + */ +interface Action { + public boolean content(OUTPUT output, String text); +} diff --git a/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java b/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java new file mode 100644 index 0000000..bffe447 --- /dev/null +++ b/core/src/main/java/org/onap/aaf/cadi/wsse/Match.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package org.onap.aaf.cadi.wsse; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; + +/** + * Match Class allows you to build an automatic Tree of StAX (or StAX like) + * Objects for frequent use. + * + * OBJECT is a type which you which to do some end Actions on, similar to a Visitor pattern, see Action + * + * Note: We have implemented with XReader and XEvent, rather than StAX for performance reasons. + * + * @see Action + * @see Match + * @see XEvent + * @see XReader + * + * + * @param + */ +//@SuppressWarnings("restriction") +public class Match { + private QName qname; + private Match[] next; + private Match prev; + private Action action = null; + private boolean stopAfter; + private boolean exclusive; + + + @SafeVarargs + public Match(String ns, String name, Match ... next) { + this.qname = new QName(ns,name); + this.next = next; + stopAfter = exclusive = false; + for(Match m : next) { // add the possible tags to look for + if(!m.stopAfter)m.prev = this; + } + } + + public Match onMatch(OUTPUT output, XReader reader) throws XMLStreamException { + while(reader.hasNext()) { + XEvent event = reader.nextEvent(); + switch(event.getEventType()) { + case XMLEvent.START_ELEMENT: + QName e_qname = event.asStartElement().getName(); + //System.out.println("Start - " + e_qname); + boolean match = false; + for(Match m : next) { + if(e_qname.equals(m.qname)) { + match=true; + if(m.onMatch(output, reader)==null) { + return null; // short circuit Parsing + } + break; + } + } + if(exclusive && !match) // When Tag MUST be present, i.e. the Root Tag, versus info we're not interested in + return null; + break; + case XMLEvent.CHARACTERS: + //System.out.println("Data - " +event.asCharacters().getData()); + if(action!=null) { + if(!action.content(output,event.asCharacters().getData())) { + return null; + } + } + break; + case XMLEvent.END_ELEMENT: + //System.out.println("End - " + event.asEndElement().getName()); + if(event.asEndElement().getName().equals(qname)) { + return prev; + } + break; + case XMLEvent.END_DOCUMENT: + return null; // Exit Chain + } + } + return this; + } + + /** + * When this Matched Tag has completed, Stop parsing and end + * @return + */ + public Match stopAfter() { + stopAfter = true; + return this; + } + + /** + * Mark that this Object MUST be matched at this level or stop parsing and end + * + * @param action + * @return + */ + public Match exclusive() { + exclusive = true; + return this; + } + + public Match set(Action action) { + this.action = action; + return this; + } +} diff --git a/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java b/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java new file mode 100644 index 0000000..760020a --- /dev/null +++ b/core/src/main/java/org/onap/aaf/cadi/wsse/WSSEParser.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package org.onap.aaf.cadi.wsse; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.stream.XMLStreamException; + +import org.onap.aaf.cadi.BasicCred; + + +/** + * WSSE Parser + * + * Read the User and Password from WSSE Formatted SOAP Messages + * + * This class uses StAX so that processing is stopped as soon as the Security User/Password are read into BasicCred, or the Header Ends + * + * This class is intended to be created once (or very few times) and reused as much as possible. + * + * It is as thread safe as StAX parsing is. + * + */ +public class WSSEParser { + private static final String SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/"; + private static final String WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; + private Match parseTree; + //private XMLInputFactory inputFactory; + + public WSSEParser() { + // soap:Envelope/soap:Header/wsse:Security/wsse:UsernameToken/[wsse:Password&wsse:Username] + parseTree = new Match(SOAP_NS,"root", // need a root level to start from... Doesn't matter what the tag is + new Match(SOAP_NS,"Envelope", + new Match(SOAP_NS,"Header", + new Match(WSSE_NS,"Security", + new Match(WSSE_NS,"UsernameToken", + new Match(WSSE_NS,"Password").set(new Action() { + public boolean content(BasicCred bc,String text) { + bc.setCred(text.getBytes()); + return true; + } + }), + new Match(WSSE_NS,"Username").set(new Action() { + public boolean content(BasicCred bc,String text) { + bc.setUser(text); + return true; + } + }) + ).stopAfter() // if found, end when UsernameToken ends (no further processing needed) + ) + ).stopAfter() // Stop Processing when Header Ends + ).exclusive()// Envelope must match Header, and no other. FYI, Body comes after Header short circuits (see above), so it's ok + ).exclusive(); // root must be Envelope + //inputFactory = XMLInputFactory.newInstance(); + } + + public XMLStreamException parse(BasicCred bc, InputStream is) throws IOException { + try { + parseTree.onMatch(bc, new XReader(is)); + return null; + } catch (XMLStreamException e) { + return e; + } + } +} diff --git a/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java b/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java new file mode 100644 index 0000000..e5af256 --- /dev/null +++ b/core/src/main/java/org/onap/aaf/cadi/wsse/XEvent.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package org.onap.aaf.cadi.wsse; + +import javax.xml.namespace.QName; +import javax.xml.stream.events.XMLEvent; + +/** + * XEvent + * + * This mechanism mimics a minimal portion of StAX "XMLEvent", enough to work with minimal XReader. + * + * We implement the same interface, as much as minimally necessary, as XMLEvent for these small usages so as to + * be interchangeable in the future, if so desired + * + * + */ +// @SuppressWarnings("restriction") +public abstract class XEvent { + + public abstract int getEventType(); + + public StartElement asStartElement() { + return (StartElement)this; + } + + public Characters asCharacters() { + return (Characters)this; + } + + public EndElement asEndElement() { + return (EndElement)this; + } + + public static abstract class NamedXEvent extends XEvent { + private QName qname; + + public NamedXEvent(QName qname) { + this.qname = qname; + } + + public QName getName() { + return qname; + } + } + public static class StartElement extends NamedXEvent { + + public StartElement(String ns, String tag) { + super(new QName(ns,tag)); + } + + @Override + public int getEventType() { + return XMLEvent.START_ELEMENT; + } + } + + public static class EndElement extends NamedXEvent { + public EndElement(String ns, String tag) { + super(new QName(ns,tag)); + } + + @Override + public int getEventType() { + return XMLEvent.END_ELEMENT; + } + } + + public static class Characters extends XEvent { + private String data; + + public Characters(String data) { + this.data = data; + } + @Override + public int getEventType() { + return XMLEvent.CHARACTERS; + } + + public String getData() { + return data; + } + } + + public static class StartDocument extends XEvent { + + @Override + public int getEventType() { + return XMLEvent.START_DOCUMENT; + } + + } + + public static class EndDocument extends XEvent { + + @Override + public int getEventType() { + return XMLEvent.END_DOCUMENT; + } + + } + public static class Comment extends XEvent { + public final String value; + public Comment(String value) { + this.value = value; + } + + @Override + public int getEventType() { + return XMLEvent.COMMENT; + } + + } + +} diff --git a/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java b/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java new file mode 100644 index 0000000..8fa8113 --- /dev/null +++ b/core/src/main/java/org/onap/aaf/cadi/wsse/XReader.java @@ -0,0 +1,416 @@ +/******************************************************************************* + * ============LICENSE_START==================================================== + * * org.onap.aaf + * * =========================================================================== + * * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * * =========================================================================== + * * 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. + * * ============LICENSE_END==================================================== + * * + * * ECOMP is a trademark and service mark of AT&T Intellectual Property. + * * + ******************************************************************************/ +package org.onap.aaf.cadi.wsse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import javax.xml.stream.XMLStreamException; + +/** + * XReader + * This class works similarly as StAX, except StAX has more behavior than is needed. That would be ok, but + * StAX also was Buffering in their code in such as way as to read most if not all the incoming stream into memory, + * defeating the purpose of pre-reading only the Header + * + * This Reader does no back-tracking, but is able to create events based on syntax and given state only, leaving the + * Read-ahead mode of the InputStream up to the other classes. + * + * At this time, we only implement the important events, though if this is good enough, it could be expanded, perhaps to + * replace the original XMLReader from StAX. + * + * + */ +// @SuppressWarnings("restriction") +public class XReader { + private XEvent curr,another; + private InputStream is; + private ByteArrayOutputStream baos; + private int state, count, last; + + private Stack> nsses; + + public XReader(InputStream is) { + this.is = is; + curr = another = null; + baos = new ByteArrayOutputStream(); + state = BEGIN_DOC; + count = 0; + nsses = new Stack>(); + } + + public boolean hasNext() throws XMLStreamException { + if(curr==null) { + curr = parse(); + } + return curr!=null; + } + + public XEvent nextEvent() { + XEvent xe = curr; + curr = null; + return xe; + } + + // + // State Flags + // + // Note: The State of parsing XML can be complicated. There are too many to cleanly keep in "booleans". Additionally, + // there are certain checks that can be better made with Bitwise operations within switches + // Keeping track of state this way also helps us to accomplish logic without storing any back characters except one + private final static int BEGIN_DOC= 0x000001; + private final static int DOC_TYPE= 0x000002; + private final static int QUESTION_F= 0x000004; + private final static int QUESTION = 0x000008; + private final static int START_TAG = 0x000010; + private final static int END_TAG = 0x000020; + private final static int VALUE= 0x000040; + private final static int COMMENT = 0x001000; + private final static int COMMENT_E = 0x002000; + private final static int COMMENT_D1 =0x010000; + private final static int COMMENT_D2 =0x020000; + private final static int COMMENT_D3 =0x040000; + private final static int COMMENT_D4 =0x080000; + // useful combined Comment states + private final static int IN_COMMENT=COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2; + private final static int COMPLETE_COMMENT = COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2|COMMENT_D3|COMMENT_D4; + + + private XEvent parse() throws XMLStreamException { + Map nss = nsses.isEmpty()?null:nsses.peek(); + + XEvent rv; + if((rv=another)!=null) { // "another" is a tag that may have needed to be created, but not + // immediately returned. Save for next parse. If necessary, this could be turned into + // a FIFO storage, but a single reference is enough for now. + another = null; // "rv" is now set for the Event, and will be returned. Set to Null. + } else { + boolean go = true; + int c=0; + + try { + while(go && (c=is.read())>=0) { + ++count; + switch(c) { + case '<': // Tag is opening + state|=~BEGIN_DOC; // remove BEGIN_DOC flag, this is possibly an XML Doc + XEvent cxe = null; + if(baos.size()>0) { // If there are any characters between tags, we send as Character Event + String chars = baos.toString().trim(); // Trim out WhiteSpace before and after + if(chars.length()>0) { // don't send if Characters were only whitespace + cxe = new XEvent.Characters(chars); + baos.reset(); + go = false; + } + } + last = c; // make sure "last" character is set for use in "ParseTag" + Tag t = parseTag(); // call subroutine to process the tag as a unit + String ns; + switch(t.state&(START_TAG|END_TAG)) { + case START_TAG: + nss = getNss(nss,t); // Only Start Tags might have NS Attributes + // Get any NameSpace elements from tag. If there are, nss will become + // a new Map with all the previous NSs plus the new. This provides + // scoping behavior when used with the Stack + // drop through on purpose + case END_TAG: + ns = t.prefix==null?"":nss.get(t.prefix); // Get the namespace from prefix (if exists) + break; + default: + ns = ""; + } + if(ns==null) + throw new XMLStreamException("Invalid Namespace Prefix at " + count); + go = false; + switch(t.state) { // based on + case DOC_TYPE: + rv = new XEvent.StartDocument(); + break; + case COMMENT: + rv = new XEvent.Comment(t.value); + break; + case START_TAG: + rv = new XEvent.StartElement(ns,t.name); + nsses.push(nss); // Change potential scope for Namespace + break; + case END_TAG: + rv = new XEvent.EndElement(ns,t.name); + nss = nsses.pop(); // End potential scope for Namespace + break; + case START_TAG|END_TAG: // This tag is both start/end aka + rv = new XEvent.StartElement(ns,t.name); + if(last=='/')another = new XEvent.EndElement(ns,t.name); + } + if(cxe!=null) { // if there is a Character Event, it actually should go first. ow. + another = rv; // Make current Event the "another" or next event, and + rv = cxe; // send Character Event now + } + break; + case ' ': + case '\t': + case '\n': + if((state&BEGIN_DOC)==BEGIN_DOC) { // if Whitespace before doc, just ignore + break; + } + // fallthrough on purpose + default: + if((state&BEGIN_DOC)==BEGIN_DOC) { // if there is any data at the start other than XML Tag, it's not XML + throw new XMLStreamException("Parse Error: This is not an XML Doc"); + } + baos.write(c); // save off Characters + } + last = c; // Some processing needs to know what the last character was, aka Escaped characters... ex \" + } + } catch (IOException e) { + throw new XMLStreamException(e); // all errors parsing will be treated as XMLStreamErrors (like StAX) + } + if(c==-1 && (state&BEGIN_DOC)==BEGIN_DOC) { // Normally, end of stream is ok, however, we need to know if the + throw new XMLStreamException("Premature End of File"); // document isn't an XML document, so we throw exception if it + } // hasn't yet been determined to be an XML Doc + } + return rv; + } + + /** + * parseTag + * + * Parsing a Tag is somewhat complicated, so it's helpful to separate this process from the + * higher level Parsing effort + * @return + * @throws IOException + * @throws XMLStreamException + */ + private Tag parseTag() throws IOException, XMLStreamException { + Tag tag = null; + boolean go = true; + state = 0; + int c, quote=0; // If "quote" is 0, then we're not in a quote. We set ' (in pretag) or " in attribs accordingly to denote quoted + String prefix=null,name=null,value=null; + baos.reset(); + + while(go && (c=is.read())>=0) { + ++count; + if(quote!=0) { // If we're in a quote, we only end if we hit another quote of the same time, not preceded by \ + if(c==quote && last!='\\') { + quote=0; + } else { + baos.write(c); + } + } else if((state&COMMENT)==COMMENT) { // similar to Quote is being in a comment + switch(c) { + case '-': + switch(state) { // XML has a complicated Quote set... ... we keep track if each has been met with flags. + case COMMENT|COMMENT_E: + state|=COMMENT_D1; + break; + case COMMENT|COMMENT_E|COMMENT_D1: + state|=COMMENT_D2; + baos.reset(); // clear out "!--", it's a Comment + break; + case COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2: + state|=COMMENT_D3; + baos.write(c); + break; + case COMMENT|COMMENT_E|COMMENT_D1|COMMENT_D2|COMMENT_D3: + state|=COMMENT_D4; + baos.write(c); + break; + } + break; + case '>': // Tag indicator has been found, do we have all the comment characters in line? + if((state&COMPLETE_COMMENT)==COMPLETE_COMMENT) { + byte ba[] = baos.toByteArray(); + tag = new Tag(null,null, new String(ba,0,ba.length-2)); + baos.reset(); + go = false; + break; + } + // fall through on purpose + default: + state&=~(COMMENT_D3|COMMENT_D4); + if((state&IN_COMMENT)!=IN_COMMENT) state&=~IN_COMMENT; // false alarm, it's not actually a comment + baos.write(c); + } + } else { // Normal Tag Processing loop + switch(c) { + case '?': + switch(state & (QUESTION_F|QUESTION)) { // Validate the state of Doc tag... + case QUESTION_F: + state |= DOC_TYPE; + state &= ~QUESTION_F; + break; + case 0: + state |=QUESTION_F; + break; + default: + throw new IOException("Bad character [?] at " + count); + } + break; + case '!': + if(last=='<') { + state|=COMMENT|COMMENT_E; // likely a comment, continue processing in Comment Loop + } + baos.write(c); + break; + case '/': + state|=(last=='<'?END_TAG:(END_TAG|START_TAG)); // end tag indicator , ,or both + break; + case ':': + prefix=baos.toString(); // prefix indicator + baos.reset(); + break; + case '=': // used in Attributes + name=baos.toString(); + baos.reset(); + state|=VALUE; + break; + case '>': // end the tag, which causes end of this subprocess as well as formulation of the found data + go = false; + // passthrough on purpose + case ' ': + case '\t': + case '\n': // white space indicates change in internal tag state, ex between name and between attributes + if((state&VALUE)==VALUE) { + value = baos.toString(); // we're in VALUE state, add characters to Value + } else if(name==null) { + name = baos.toString(); // we're in Name state (default) add characters to Name + } + baos.reset(); // we've assigned chars, reset buffer + if(name!=null) { // Name is not null, there's a tag in the offing here... + Tag t = new Tag(prefix,name,value); + if(tag==null) { // Set as the tag to return, if not exists + tag = t; + } else { // if we already have a Tag, then we'll treat this one as an attribute + tag.add(t); + } + } + prefix=name=value=null; // reset these values in case we loop for attributes. + break; + case '\'': // is the character one of two kinds of quote? + case '"': + if(last!='\\') { + quote=c; + break; + } + // Fallthrough ok + default: + baos.write(c); // write any unprocessed bytes into buffer + + } + } + last = c; + } + int type = state&(DOC_TYPE|COMMENT|END_TAG|START_TAG); // get just the Tag states and turn into Type for Tag + if(type==0) { + type=START_TAG; + } + tag.state|=type; // add the appropriate Tag States + return tag; + } + + /** + * getNSS + * + * If the tag contains some Namespace attributes, create a new nss from the passed in one, copy all into it, then add + * This provides Scoping behavior + * + * if Nss is null in the first place, create an new nss, so we don't have to deal with null Maps. + * + * @param nss + * @param t + * @return + */ + private Map getNss(Map nss, Tag t) { + Map newnss = null; + if(t.attribs!=null) { + for(Tag tag : t.attribs) { + if("xmlns".equals(tag.prefix)) { + if(newnss==null) { + newnss = new HashMap(); + if(nss!=null)newnss.putAll(nss); + } + newnss.put(tag.name, tag.value); + } + } + } + return newnss==null?(nss==null?new HashMap():nss):newnss; + } + + /** + * The result of the parseTag method + * + * Data is split up into prefix, name and value portions. "Tags" with Values that are inside a Tag are known in XLM + * as Attributes. + * + * + */ + public class Tag { + public int state; + public String prefix,name,value; + public List attribs; + + public Tag(String prefix, String name, String value) { + this.prefix = prefix; + this.name = name; + this.value = value; + attribs = null; + } + + /** + * add an attribute + * Not all tags need attributes... lazy instantiate to save time and memory + * @param tag + */ + public void add(Tag attrib) { + if(attribs == null) { + attribs = new ArrayList(); + } + attribs.add(attrib); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + if(prefix!=null) { + sb.append(prefix); + sb.append(':'); + } + sb.append(name==null?"!!ERROR!!":name); + + char quote = ((state&DOC_TYPE)==DOC_TYPE)?'\'':'"'; + if(value!=null) { + sb.append('='); + sb.append(quote); + sb.append(value); + sb.append(quote); + } + return sb.toString(); + } + } + +} -- cgit 1.2.3-korg