Skip to content

Commit

Permalink
Merge pull request #5 from olivergondza/namespaces
Browse files Browse the repository at this point in the history
Add support for XML namespaces
  • Loading branch information
olivergondza authored May 30, 2024
2 parents 38aacc4 + fe12b91 commit f06657d
Show file tree
Hide file tree
Showing 11 changed files with 782 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/main/java/com/github/olivergondza/saxeed/Saxeed.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ private SAXParser getSaxParser() {
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setNamespaceAware(true);
return factory.newSAXParser();
} catch (ParserConfigurationException | SAXException e) {
throw new AssertionError("SAX or its essential features are not supported", e);
Expand Down
120 changes: 85 additions & 35 deletions src/main/java/com/github/olivergondza/saxeed/Subscribed.java
Original file line number Diff line number Diff line change
@@ -1,57 +1,107 @@
package com.github.olivergondza.saxeed;

import java.util.Arrays;
import java.util.List;

/**
* Criteria for Visitor-to-tags subscription.
*/
public abstract class Subscribed {
@FunctionalInterface
public interface Subscribed {

/**
* Subscribe to all tags in the document.
*/
static Subscribed toAll() {
return Builder.ALL;
}

/**
* Build subscription criteria.
*/
static Subscribed.Builder to() {
return new Subscribed.Builder();
}

boolean isSubscribed(TagName tagName);

private static final Subscribed ALL = new Subscribed() {
@Override
public boolean isSubscribed(String tagName) {
return true;
final class Builder {
private static final Subscribed ALL = tagName -> true;

private Subscribed nsFilter = ALL;
private Subscribed tagFilter = ALL;

Builder() {
}
};

private static final Subscribed NONE = new Subscribed() {
@Override
public boolean isSubscribed(String tagName) {
return false;
/**
* Match tags regardless of namespace status.
*/
public Builder anyNamespace() {
nsFilter = ALL;
return this;
}
};

public static Subscribed toAll() {
return ALL;
}
/**
* Match tags in default, not overridden namespace.
*/
public Builder noNamespace() {
nsFilter = name -> name.getNsUri().isEmpty();
return this;
}

public static Subscribed to(String... tagNames) {
return tagNames.length == 0
? NONE
: new Tags(tagNames)
;
}
/**
* Match tags in default namespace, named or not.
*/
public Builder defaultNamespace() {
nsFilter = name -> name.getNsPrefix().isEmpty();
return this;
}

public abstract boolean isSubscribed(String tagName);
/**
* Match tags in namespace its uri is in the arguments.
*/
public Builder namespaceUris(String... uris) {
List<String> namespaces = list("namespace", uris);
nsFilter = name -> namespaces.contains(name.getNsUri());
return this;
}

private static final class Tags extends Subscribed {
/**
* Match any local tag name.
*/
public Builder anyTag() {
tagFilter = ALL;
return this;
}

private final String[] tagNames;
/**
* Match local tag names specified in arguments.
*/
public Builder tagNames(String... locals) {
List<String> tags = list("tag", locals);
tagFilter = name -> tags.contains(name.getLocal());
return this;
}

public Tags(String... tagNames) {
this.tagNames = tagNames;
for (String name: tagNames) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Empty tag name specified for subscription");
}
}
public Subscribed build() {
assert nsFilter != null;
assert tagFilter != null;

return name -> tagFilter.isSubscribed(name) && nsFilter.isSubscribed(name);
}

@Override
public boolean isSubscribed(String tagName) {
for (String name: tagNames) {
if (name.equals(tagName)) return true;
private static List<String> list(String type, String[] vals) {
if (vals.length == 0) {
throw new IllegalArgumentException("Subscribing to 0 " + type + "s means no subscription at all");
}

return false;
for (String name: vals) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Empty " + type + " name specified for subscription in: " + Arrays.toString(vals));
}
}
return List.of(vals);
}
}
}
23 changes: 22 additions & 1 deletion src/main/java/com/github/olivergondza/saxeed/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ public interface Tag {
/**
* Get element name.
*/
String getName();
TagName getName();

/**
* Determine if current tag's name is @name.
*/
boolean isNamed(String name);
boolean isNamed(TagName name);

/**
* Get Tag's parent
Expand All @@ -33,13 +34,15 @@ public interface Tag {
* @return null for root tag, or when parent name differs, parent otherwise.
*/
Tag getParent(String name);
Tag getParent(TagName name);

/**
* Get the closest ancestor (wrapping tag) its name is @name.
*
* @return null if there is no such ancestor, ancestor otherwise.
*/
Tag getAncestor(String name);
Tag getAncestor(TagName name);

/**
* Get modifiable attribute map.
Expand Down Expand Up @@ -98,6 +101,8 @@ interface Start extends Tag {
*/
Tag.Start addChild(String name);

Tag.Start addChild(TagName name);

/**
* Add new parent element for the current tag.
*
Expand All @@ -107,10 +112,22 @@ interface Start extends Tag {
*/
Tag.Start wrapWith(String name);

Tag.Start wrapWith(TagName name);

/**
* Set text to write after opening tag.
*/
void addText(String text);

/**
* Declare new namespace on this tag.
*
* Note that tag name will not be converted to us this prefix
*
* @param uri Namespace URI
* @param prefix Namespace prefix, can be "" for default namespace.
*/
void declareNamespace(String uri, String prefix);
}

interface Chars extends Tag {
Expand All @@ -124,6 +141,8 @@ interface Chars extends Tag {
*/
Tag.Start addChild(String name);

Tag.Start addChild(TagName name);

/**
* Set text to write before closing tag.
*/
Expand All @@ -145,6 +164,8 @@ interface End extends Tag {
*/
Tag.Start addChild(String name);

Tag.Start addChild(TagName name);

/**
* Set text to write before closing tag.
*/
Expand Down
86 changes: 86 additions & 0 deletions src/main/java/com/github/olivergondza/saxeed/TagName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.github.olivergondza.saxeed;

import java.util.Objects;

/**
* Namespace aware tag name.
*/
public class TagName {

private final String local;
private final String qName;

private final String uri;
private final String prefix;

public static TagName fromSaxArgs(String uri, String localName, String qName) {
boolean noNsInTagName = Objects.equals(localName, qName);
if (uri.isEmpty()) {
assert noNsInTagName;
return new TagName("", "", localName);
}

assert qName.endsWith(localName): String.format("Tag name ('%s') does not start with local name ('%s')", qName, localName);

if (noNsInTagName) {
return new TagName(uri, "", localName);
}

return new TagName(uri, qName.replaceFirst(":.*", ""), localName);
}

public static TagName noNs(String local) {
return new TagName("", "", local);
}

public static TagName withNs(String uri, String local) {
return new TagName(uri, "", local);
}

public static TagName withNs(String uri, String prefix, String local) {
return new TagName(uri, prefix, local);
}

/**
* Create tag in specified namespace.
*/
public TagName(String uri, String prefix, String local) {
this.uri = Objects.requireNonNull(uri);
this.prefix = Objects.requireNonNull(prefix);
this.local = Objects.requireNonNull(local);

if (local.isEmpty()) throw new IllegalArgumentException("Tag cannot have local name an empty string");

if (uri.isEmpty() && !prefix.isEmpty()) throw new IllegalArgumentException("Tag cannot have NS name, but no NS URI");

qName = prefix.isEmpty()
? local
: prefix + ":" + local
;
}

@Override
public String toString() {
return String.format("TagName{local='%s', uri='%s', ns='%s'}", local, uri, prefix);
}

public String getNsUri() {
return uri;
}

public String getNsPrefix() {
return prefix;
}

public String getLocal() {
return local;
}

public String getQualifiedName() {
return qName;
}

public TagName inheritNamespace(String name) {
return new TagName(uri, prefix, name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public TransformationBuilder() {
}

public TransformationBuilder add(String tagName, List<UpdatingVisitor> visitors) {
return add(Subscribed.to(tagName), visitors);
return add(Subscribed.to().tagNames(tagName).build(), visitors);
}

public TransformationBuilder add(String tagName, UpdatingVisitor visitor) {
return add(Subscribed.to(tagName), visitor);
return add(Subscribed.to().tagNames(tagName).build(), visitor);
}

public TransformationBuilder add(Subscribed subs, UpdatingVisitor visitor) {
Expand Down
Loading

0 comments on commit f06657d

Please sign in to comment.