Standard Libraries

/** --- XML ---

   Classes to represent XML files and their content.
*/

import semmle.code.Location

/** An XMLParent is either an XMLElement or an XMLFile. 
 *   Both classes can contain other elements and are therefore parents. */
class XMLParent extends @xmlparent {
   /** get this parent's name; should be overridden in subclasses */
   /*abstract*/ string getName() { result = "parent" }

   /** get the file this XML parent comes from */
   XMLFile getFile() { result = this or xmlElements(this,_,_,_,result) }

   /** get an element that is a child of this parent */
   XMLElement getAChild() { xmlElements(result,_,this,_,_) }

   /** get a comment that is a child of this parent */
   XMLComment getAComment() { xmlComments(result,_,this,_)   }

   /** get a characters sequence that is a child of this parent */
   XMLCharacters getACharactersSet() { xmlChars(result,_,this,_,_,_)  }
   
   /** gets the depth in the tree. Overridden in XMLElement. */
   int getDepth() { result = 0 }   

   /** computes the number of children*/
   int getNumberOfChildren() { 
       result = count(XMLElement e | xmlElements(e,_,this,_,_)) 
   }

   /** gets the number of places in the body of an XML parent where text occurs */
   int getNumberOfCharacterSets() { 
       result = count(int pos | xmlChars(_,_,this,pos,_,_)) 
   }

   /** appends the character sets from left to right separated by a space */
   string charsSetUpTo(int n) {
      (n = 0 and xmlChars(_,result,this,0,_,_)) or
      (n > 0 and exists(string chars| xmlChars(_,chars,this,n,_,_) and
                           result = this.charsSetUpTo(n-1) + " " + chars))
   }

   /** produces the string that puts all chars next to each other */
   string allCharactersString() {
      exists(int n | n = this.getNumberOfCharacterSets() and
        ((n=0 and result = "") or
         (n>0 and result = this.charsSetUpTo(n-1))))
   }

   /** printable representation */
   string toString()    { result = this.getName() }  

   /** set icon to represent this parent */
   string getIconPath() { result = "icons/tag.png" }

}

/** A parsed XML file */
class XMLFile extends @xmlfile, XMLParent, File {
   string toString() { result = XMLParent.super.toString() }

   /** get this file's name */
   string getName()     { files(this,result,_,_,_) }

   /** get this file's path */
   string getPath()     { files(this,_,result,_,_) }

   /** get this file's folder */
   string getFolder()   { 
     result = this.getPath().substring(1, 
                               this.getPath().length()-this.getName().length())
   }

   /** get this file's enconding */
   string getEncoding() { xmlEncoding(this,result) }
   
   /** get the file this entity belongs to */
   XMLFile getFile()    { result = this }

   /** gives a top most element in an XML file*/
   XMLElement getARootElement() { result = this.getAChild() }
   
   /** gives a DTD*/
   XMLDTD getADTD() { xmlDTDs(result,_,_,_,this) }
  
   /** set icon to represent this file */
   string getIconPath() { result = "icons/file.png" }
}

/** A Document Type Declarations of an XML file */
class XMLDTD extends @xmldtd {
   /** get this DTD's root element name */
   string getRoot()      { xmlDTDs(this,result,_,_,_) }

   /** get this DTD's public ID */
   string getPublicId()  { xmlDTDs(this,_,result,_,_) }

   /** get this DTD's system ID */
   string getSystemId()  { xmlDTDs(this,_,_,result,_) }

   /** is this DTD public? */
   predicate isPublic()  { not xmlDTDs(this,_,"",_,_) }

   /** get this DTD's parent */
   XMLParent getParent() { xmlDTDs(this,_,_,_,result) }

   /** printable representation */
   string toString() { 
      (this.isPublic() and result = this.getRoot() + " PUBLIC '" + 
                                    this.getPublicId() + "' '" + 
                                    this.getSystemId() + "'") or
      (not this.isPublic() and result = this.getRoot() + 
                                        " SYSTEM '" + 
                                        this.getSystemId() + "'")
   }
}

/** An XML tag in an XML file */
class XMLElement extends @xmlelement, XMLParent {
   /** get this element's name */
   string getName() { xmlElements(this,result,_,_,_) }
   
   /** the qualified name is of the following form:
    * <namespace>:<name> or <name> if no namespace for this element is present */
   string getQualifiedName() { 
      exists(XMLNamespace n | this.getNamespace()=n and not n.isDefault() and 
                              result = n.getPrefix() + ":" + this.getName()) or
      (not exists(XMLNamespace n | this.getNamespace()=n and not n.isDefault()) 
       and result = this.getName())
   }

   /** get the file this element appears in */
   XMLFile getFile()             { xmlElements(this,_,_,_,result) }

   /** get this element's parent */
   XMLParent getParent()         { xmlElements(this,_,result,_,_) }

   /** does this element have a namespace? */
   predicate hasNamespace()      { xmlHasNs(this,_,_) }

   /** get this element's namespace (if any) */
   XMLNamespace getNamespace()   { xmlHasNs(this,result,_) }

   /** get the index of this element */
   int getElementPositionIndex() { xmlElements(this,_,_,result,_) }

   /** gets the depth of this element */
   int getDepth() { result = this.getParent().getDepth() + 1 }

   /** gives an attribute of this element */
   XMLAttribute getAnAttribute() { result.getElement() = this }

   /** gives an attribute with a specific name */
   XMLAttribute getAttribute(string name) { 
      result.getElement() = this and result.getName() = name 
   }

   /** does this element have an attribute name? */
   predicate hasAttribute(string name) {
      exists(XMLAttribute a| a = this.getAttribute(name))
   }

   /** gives the value of a specific attribute */
   string getAttributeValue(string name) { 
      result = this.getAttribute(name).getValue() 
   }

   /** source location for this element */
   Location getLocation() { hasLocation(this,result) }

   /** URL for this element */
   string getURL() { 
     if exists(this.getLocation()) then 
       result = this.getLocation().getURL()
     else
       result = ""
   }
}

/** An attribute that occurs inside an XML element */
class XMLAttribute extends @xmlattribute {
   /** get this attribute's name */
   string getName() { xmlAttrs(this,_,result,_,_,_) }
   
   /** same as for XML element, the qualified name is of the following form:*/
   /** <namespace>:<name> or <name> if no namespace for this attribute is present*/
   string getQualifiedName() { 
      exists(XMLNamespace n | this.getNamespace()=n and not n.isDefault() and 
                              result = n.getPrefix() + ":" + this.getName()) or
      (not exists(XMLNamespace n | this.getNamespace()=n and not n.isDefault()) 
       and result = this.getName())
   }
    
   /** get the element this attribute belongs to */
   XMLElement getElement()     { xmlAttrs(this,result,_,_,_,_) }

   /** does this attribute have a namespace? */
   predicate hasNamespace()    { xmlHasNs(this,_,_) }

   /** get this attribute's namespace (if any) */
   XMLNamespace getNamespace() { xmlHasNs(this,result,_) }
   
   /** get this attribute's value */
   string getValue()    { xmlAttrs(this,_,_,result,_,_) }

   /** printable representation */
   string toString()    { result = this.getName() + "=" + this.getValue() }

   /** set icon to represent this attribute */
   string getIconPath() { result = "icons/publicfield.png" }
}

/** A namespace used in an XML file */
class XMLNamespace extends @xmlnamespace {
   /** get the namespace prefix */
   string getPrefix() { xmlNs(this,result,_,_) }

   /** get the namespace URI */
   string getURI()    { xmlNs(this,_,result,_) }

   /** if this namespace has no prefix, then it is default */
   predicate isDefault() { this.getPrefix() = "" }
   
   /** printable representation */
   string toString() { 
      (this.isDefault() and result = this.getURI()) or
      (not this.isDefault() and result = this.getPrefix() + ":" + this.getURI())
   }

   /** set icon to represent this namespace */
   string getIconPath() { result = "icons/publicfield.png" }
}

/** A comment of the form <!-- ... --> */
class XMLComment extends @xmlcomment {
   /** get comment content */
   string getText()      { xmlComments(this,result,_,_) }

   /** get parent of this comment */
   XMLParent getParent() { xmlComments(this,_,result,_) }

   /** printable representation */
   string toString()    { result = this.getText() }

   /** set icon to represent this comment */
   string getIconPath() { result = "icons/publicfield.png" }
}

/* A sequence of characters that occurs between opening and 
*  closing tags of an XML element, excluding other elements */
class XMLCharacters extends @xmlcharacters {
   /** get content of this character sequence */
   string getCharacters() { xmlChars(this,result,_,_,_,_) }

   /** get the parent of this character sequence */
   XMLParent getParent()  { xmlChars(this,_,result,_,_,_) }

   /** is this character sequence CDATA? */
   predicate isCDATA()    { xmlChars(this,_,_,_,true,_) }

   /** printable representation */
   string toString()    { result = this.getCharacters() }

   /** set icon to represent this character sequence */
   string getIconPath() { result = "icons/publicfield.png" }
}