AttrsXmlParserpublic final class AttrsXmlParser extends Object Parser for attributes description files. |
Fields Summary |
---|
private Document | mDocument | private String | mOsAttrsXmlPath | private HashMap | mAttributeMap | private HashMap | mStyleMapMap of all attribute names for a given element | private Map | mEnumFlagValuesMap of all (constant, value) pairs for attributes of format enum or flag.
E.g. for attribute name=gravity, this tells us there's an enum/flag called "center"
with value 0x11. |
Constructors Summary |
---|
public AttrsXmlParser(String osAttrsXmlPath)Creates a new {@link AttrsXmlParser}, set to load things from the given
XML file. Nothing has been parsed yet. Callers should call {@link #preload()}
next.
this(osAttrsXmlPath, null /* inheritableAttributes */);
| public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes)Creates a new {@link AttrsXmlParser} set to load things from the given
XML file. If inheritableAttributes is non-null, it must point to a preloaded
{@link AttrsXmlParser} which attributes will be used for this one. Since
already defined attributes are not modifiable, they are thus "inherited".
mOsAttrsXmlPath = osAttrsXmlPath;
// styles are not inheritable.
mStyleMap = new HashMap<String, DeclareStyleableInfo>();
if (inheritableAttributes == null) {
mAttributeMap = new HashMap<String, AttributeInfo>();
mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
} else {
mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap);
mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
inheritableAttributes.mEnumFlagValues);
}
|
Methods Summary |
---|
public java.util.Map | getDeclareStyleableList()Returns a list of all decleare-styleable found in the xml file.
return Collections.unmodifiableMap(mStyleMap);
| private org.w3c.dom.Document | getDocument()Creates an XML document from the attrs.xml OS path.
May return null if the file doesn't exist or cannot be parsed.
if (mDocument == null) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(false);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
mDocument = builder.parse(new File(mOsAttrsXmlPath));
} catch (ParserConfigurationException e) {
AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (SAXException e) {
AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
}
}
return mDocument;
| public java.util.Map | getEnumFlagValues()Returns a map of all enum and flag constants sorted by parent attribute name.
The map is attribute_name => (constant_name => integer_value).
return mEnumFlagValues;
| public java.lang.String | getOsAttrsXmlPath()
return mOsAttrsXmlPath;
| public void | loadLayoutParamsAttributes(com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo info)Loads all attributes for the layout data info based on the class name.
if (getDocument() != null) {
// Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout".
String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
info.getViewLayoutClass().getShortClassName(),
info.getShortClassName());
xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$
DeclareStyleableInfo style = mStyleMap.get(xmlName);
if (style != null) {
info.setAttributes(style.getAttributes());
}
}
| public void | loadViewAttributes(ViewClassInfo info)Loads all attributes & javadoc for the view class info based on the class name.
if (getDocument() != null) {
String xmlName = info.getShortClassName();
DeclareStyleableInfo style = mStyleMap.get(xmlName);
if (style != null) {
info.setAttributes(style.getAttributes());
info.setJavaDoc(style.getJavaDoc());
}
}
| private com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo | parseAttr(org.w3c.dom.Node attrNode, org.w3c.dom.Node lastComment)Parses an node and convert it into an {@link AttributeInfo} if it is valid.
AttributeInfo info = null;
Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode != null) {
String name = nameNode.getNodeValue();
if (name != null) {
info = mAttributeMap.get(name);
// If the attribute is unknown yet, parse it.
// If the attribute is know but its format is unknown, parse it too.
if (info == null || info.getFormats().length == 0) {
info = parseAttributeTypes(attrNode, name);
if (info != null) {
mAttributeMap.put(name, info);
}
} else if (lastComment != null) {
info = new AttributeInfo(info);
}
if (info != null) {
if (lastComment != null) {
info.setJavaDoc(parseJavadoc(lastComment.getNodeValue()));
info.setDeprecatedDoc(parseDeprecatedDoc(lastComment.getNodeValue()));
}
}
}
}
return info;
| private com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo | parseAttributeTypes(org.w3c.dom.Node attrNode, java.lang.String name)Returns the {@link AttributeInfo} for a specific XML node.
This gets the javadoc, the type, the name and the enum/flag values if any.
The XML node is expected to have the following attributes:
- "name", which is mandatory. The node is skipped if this is missing.
- "format".
The format may be one type or two types (e.g. "reference|color").
An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute,
they are implicitely stated by the presence of sub-nodes or .
By design, nodes of the same name MUST have the same type.
Attribute nodes are thus cached by name and reused as much as possible.
When reusing a node, it is duplicated and its javadoc reassigned.
TreeSet<AttributeInfo.Format> formats = new TreeSet<AttributeInfo.Format>();
String[] enumValues = null;
String[] flagValues = null;
Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$
if (attrFormat != null) {
for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$
try {
Format format = AttributeInfo.Format.valueOf(f.toUpperCase());
// enum and flags are handled differently right below
if (format != null &&
format != AttributeInfo.Format.ENUM &&
format != AttributeInfo.Format.FLAG) {
formats.add(format);
}
} catch (IllegalArgumentException e) {
AdtPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
f, name, getOsAttrsXmlPath());
}
}
}
// does this <attr> have <enum> children?
enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$
if (enumValues != null) {
formats.add(AttributeInfo.Format.ENUM);
}
// does this <attr> have <flag> children?
flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$
if (flagValues != null) {
formats.add(AttributeInfo.Format.FLAG);
}
AttributeInfo info = new AttributeInfo(name,
formats.toArray(new AttributeInfo.Format[formats.size()]));
info.setEnumValues(enumValues);
info.setFlagValues(flagValues);
return info;
| private DeclareStyleableInfo | parseDeclaredStyleable(java.lang.String styleName, org.w3c.dom.Node declareStyleableNode)Finds all the attributes for a particular style node,
e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout".
ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>();
Node lastComment = null;
for (Node node = declareStyleableNode.getFirstChild();
node != null;
node = node.getNextSibling()) {
switch (node.getNodeType()) {
case Node.COMMENT_NODE:
lastComment = node;
break;
case Node.ELEMENT_NODE:
if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
AttributeInfo info = parseAttr(node, lastComment);
if (info != null) {
attrs.add(info);
}
}
lastComment = null;
break;
}
}
return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()]));
| private java.lang.String | parseDeprecatedDoc(java.lang.String comment)Parses the javadoc and extract the first @deprecated tag, if any.
Returns null if there's no @deprecated tag.
The deprecated tag can be of two forms:
- {+@deprecated ...text till the next bracket }
Note: there should be no space or + between { and @. I need one in this comment otherwise
this method will be tagged as deprecated ;-)
- @deprecated ...text till the next @tag or end of the comment.
In both cases the comment can be multi-line.
// Skip if we can't even find the tag in the comment.
if (comment == null) {
return null;
}
// sanitize & collapse whitespace
comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
int pos = comment.indexOf("{@deprecated");
if (pos >= 0) {
comment = comment.substring(pos + 12 /* len of {@deprecated */);
comment = comment.replaceFirst("^([^}]*).*", "$1");
} else if ((pos = comment.indexOf("@deprecated")) >= 0) {
comment = comment.substring(pos + 11 /* len of @deprecated */);
comment = comment.replaceFirst("^(.*?)(?:@.*|$)", "$1");
} else {
return null;
}
return comment.trim();
| private java.lang.String[] | parseEnumFlagValues(org.w3c.dom.Node attrNode, java.lang.String filter, java.lang.String attrName)Given an XML node that represents an node, this method searches
if the node has any children nodes named "target" (e.g. "enum" or "flag").
Such nodes must have a "name" attribute.
If "attrNode" is null, look for any that has the given attrNode
and the requested children nodes.
This method collects all the possible names of these children nodes and
return them.
ArrayList<String> names = null;
for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode == null) {
AdtPlugin.log(IStatus.WARNING,
"Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
attrName, filter);
} else {
if (names == null) {
names = new ArrayList<String>();
}
String name = nameNode.getNodeValue();
names.add(name);
Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$
if (valueNode == null) {
AdtPlugin.log(IStatus.WARNING,
"Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
attrName, filter, name);
} else {
String value = valueNode.getNodeValue();
try {
int i = value.startsWith("0x") ?
Integer.parseInt(value.substring(2), 16 /* radix */) :
Integer.parseInt(value);
Map<String, Integer> map = mEnumFlagValues.get(attrName);
if (map == null) {
map = new HashMap<String, Integer>();
mEnumFlagValues.put(attrName, map);
}
map.put(name, Integer.valueOf(i));
} catch(NumberFormatException e) {
AdtPlugin.log(e,
"Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
attrName, filter, name, value);
}
}
}
}
}
return names == null ? null : names.toArray(new String[names.size()]);
| private java.lang.String | parseJavadoc(java.lang.String comment)Parses the javadoc comment.
Only keeps the first sentence.
This does not remove nor simplify links and references. Such a transformation
is done later at "display" time in {@link DescriptorsUtils#formatTooltip(String)} and co.
if (comment == null) {
return null;
}
// sanitize & collapse whitespace
comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
// Explicitly remove any @deprecated tags since they are handled separately.
comment = comment.replaceAll("(?:\\{@deprecated[^}]*\\}|@deprecated[^@}]*)", "");
// take everything up to the first dot that is followed by a space or the end of the line.
// I love regexps :-). For the curious, the regexp is:
// - start of line
// - ignore whitespace
// - group:
// - everything, not greedy
// - non-capturing group (?: )
// - end of string
// or
// - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" )
// (<! non-capturing zero-width negative look-behind)
// - a dot
// - followed by a space (?= non-capturing zero-width positive look-ahead)
// - anything else is ignored
comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
return comment;
| private void | parseResources(org.w3c.dom.Node res)Finds all the and nodes in the top node.
Node lastComment = null;
for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) {
switch (node.getNodeType()) {
case Node.COMMENT_NODE:
lastComment = node;
break;
case Node.ELEMENT_NODE:
if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$
Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode != null) {
String name = nameNode.getNodeValue();
Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$
String parents = parentNode == null ? null : parentNode.getNodeValue();
if (name != null && !mStyleMap.containsKey(name)) {
DeclareStyleableInfo style = parseDeclaredStyleable(name, node);
if (parents != null) {
style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$
}
mStyleMap.put(name, style);
if (lastComment != null) {
style.setJavaDoc(parseJavadoc(lastComment.getNodeValue()));
}
}
}
} else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
parseAttr(node, lastComment);
}
lastComment = null;
break;
}
}
| public com.android.ide.eclipse.common.resources.AttrsXmlParser | preload()Preloads the document, parsing all attributes and declared styles.
Document doc = getDocument();
if (doc == null) {
AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
Node res = doc.getFirstChild();
while (res != null &&
res.getNodeType() != Node.ELEMENT_NODE &&
!res.getNodeName().equals("resources")) { //$NON-NLS-1$
res = res.getNextSibling();
}
if (res == null) {
AdtPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
parseResources(res);
return this;
|
|