Skip to content

Commit

Permalink
feat(chars): Character support
Browse files Browse the repository at this point in the history
Listen to character events, and emit characters.
  • Loading branch information
olivergondza committed Apr 23, 2024
1 parent 9028193 commit 13bd7a2
Show file tree
Hide file tree
Showing 12 changed files with 767 additions and 76 deletions.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ Saxeed strives to add as much convenience on top of plain old SAX, while adding

### Capabilities

Each tag visitor have access to / can modify the following:

| | Tag Start | Tag End |
|------------------------------------|------------------------|------------------------|
| Access Tag attributes |||
| Access Parent(s) Tag attributes |||
| Add Child Tags || ☑ (before closing tag) |
| Add Sibling Tags (NOT IMPLEMENTED) | ☑ (before and after) | ☑ (only after) |
| Add Parent Tag (`wrapWith()`) |||
| Change Attributes |||
| Delete Tag (`unwrap()`) |||
| Delete Tag Recursively (`skip()`) |||
| Delete Child Tags (`empty()`) |||
Each tag visitor do the following:

| | Tag Start | Tag End | Text Content |
|-----------------------------------------|------------------------|------------------------|--------------|
| Access Tag attributes || | |
| Access Parent(s) Tag attributes || | |
| Add Child Tags/Text || ☑ (before closing tag) | |
| Add Sibling Tags/text (NOT IMPLEMENTED) | ☑ (before and after) | ☑ (only after) | |
| Add Parent Tag (`wrapWith()`) || | |
| Change Attributes || | |
| Delete Tag (`unwrap()`) || | n/a |
| Delete Tag Recursively (`skip()`) ||| n/a |
| Delete Child Tags (`empty()`) || | n/a |

More complex changes can be implemented by subscribing visitors to multiple tags, and retaining information between their visits.

Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/github/olivergondza/saxeed/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,28 @@ interface Start extends Tag {
* @return Tag instance created.
*/
Tag.Start wrapWith(String name);

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

interface Chars extends Tag {

/**
* Add new child element.
*
* Its attributes and children can be added after.
*
* @return Tag instance added.
*/
Tag.Start addChild(String name);

/**
* Set text to write before closing tag.
*/
void addText(String text);
}

/**
Expand All @@ -122,5 +144,10 @@ interface End extends Tag {
* @return Tag instance added.
*/
Tag.Start addChild(String name);

/**
* Set text to write before closing tag.
*/
void addText(String text);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.olivergondza.saxeed;

import com.github.olivergondza.saxeed.ex.FailedTransforming;
import com.github.olivergondza.saxeed.internal.CharChunk;

/**
* Visitor listening and modifying resulting stream.
Expand All @@ -13,6 +14,9 @@ default void startDocument() throws FailedTransforming {
default void startTag(Tag.Start tag) throws FailedTransforming {
}

default void chars(Tag.Chars tag, CharChunk chars) {
}

default void endTag(Tag.End tag) throws FailedTransforming {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.github.olivergondza.saxeed.internal;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

public class CharChunk {
private char[] origData;
private int origStart;
private int origLength;

/**
* Updated content.
*/
private String replacement;

public CharChunk() {
}

public void update(char[] charsData, int charsStart, int charsLength) {
origData = charsData;
origStart = charsStart;
origLength = charsLength;
replacement = null;
}

public void update(String text) {
replacement = text;
origData = null;
origStart = -1;
origLength = -1;
}

public void clear() {
update(null);
}

public boolean isEmpty() {
return origData == null && replacement == null;
}

public String get() {
if (replacement != null) {
return replacement;
}

// cleared
if (origData == null) {
return null;
}

// Construct the String iff some visitor really need a String instance.
// This is to prevent data copying/allocation that might not be needed.
replacement = new String(origData, origStart, origLength);
return replacement;
}

/*package*/ void write(XMLStreamWriter writer) throws XMLStreamException {
// origData are erased when content is updated
if (origData != null) {
writer.writeCharacters(origData, origStart, origLength);
} else {
writer.writeCharacters(replacement);
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/github/olivergondza/saxeed/internal/Element.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.github.olivergondza.saxeed.internal;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

/**
* Element introduced by visitor to be added to the output document.
*/
public interface Element {
/**
* Element that can write itself to XMLStreamWriter.
*/
interface SelfWriting extends Element {
void write(XMLStreamWriter writer) throws XMLStreamException;
}

final class TextString implements SelfWriting {
private final String text;

public TextString(String text) {
this.text = text;
}

@Override
public void write(XMLStreamWriter writer) throws XMLStreamException {
writer.writeCharacters(text);
}
}
}
19 changes: 14 additions & 5 deletions src/main/java/com/github/olivergondza/saxeed/internal/TagImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* provide a compile-time guarantee that operations used can legally be performed at given time. For example, when
* closing tag, method modifying attributes will not be available because they are already written.
*/
/*package*/ class TagImpl implements Tag, Tag.Start, Tag.End {
/*package*/ class TagImpl implements Element, Tag, Tag.Start, Tag.Chars, Tag.End {

private /*almost final*/ TagImpl parent;

Expand All @@ -44,7 +44,7 @@
/**
* List of children to be added.
*/
private final List<TagImpl> childrenToAdd = new ArrayList<>();
private final List<Element> childElements = new ArrayList<>();

/**
* Element that current element should be surrounded with.
Expand Down Expand Up @@ -133,10 +133,14 @@ public Tag getAncestor(String name) {
@Override
public TagImpl addChild(String name) {
TagImpl child = new TagImpl(this, name);
childrenToAdd.add(child);
childElements.add(child);
return child;
}

public void addText(String text) {
childElements.add(new Element.TextString(text));
}

@Override
public TagImpl wrapWith(String name) {
this.wrapWith = new TagImpl(parent, name);
Expand Down Expand Up @@ -180,8 +184,13 @@ public String removeAttribute(String attr) {
return getAttributes().remove(attr);
}

/*package*/ List<TagImpl> getTagsAdded() {
return childrenToAdd;
/**
* Elements that visitors decided to add.
*/
/*package*/ List<Element> consumeChildren() {
List<Element> out = new ArrayList<>(childElements);
childElements.clear();
return out;
}

// The hierarchy of tag parents needs to be fixed as we have injected a new one
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.github.olivergondza.saxeed.internal;

import com.github.olivergondza.saxeed.Subscribed;
import com.github.olivergondza.saxeed.Tag;
import com.github.olivergondza.saxeed.UpdatingVisitor;
import com.github.olivergondza.saxeed.ex.FailedTransforming;
import com.github.olivergondza.saxeed.ex.FailedWriting;
Expand Down Expand Up @@ -37,7 +36,9 @@ public class TransformationHandler extends DefaultHandler implements AutoCloseab
private final XMLStreamWriter writer;
// A resource to close once done. Can be null
private final AutoCloseable closeAction;

private TagImpl currentTag;
private final CharChunk currentChars = new CharChunk();

public TransformationHandler(
LinkedHashMap<UpdatingVisitor, Subscribed> visitors,
Expand Down Expand Up @@ -71,9 +72,10 @@ private void _startElement(TagImpl tag) {

TagImpl wrapper = tag.startWrapWith();
if (wrapper != null) {
if (!(wrapper.getTagsAdded().isEmpty())) {
List<Element> children = wrapper.consumeChildren();
if (!(children.isEmpty())) {
throw new AssertionError(
"Writing sub-elements is not supported for suround with elements: " + wrapper.getTagsAdded()
"Writing sub-elements is not supported for surround with elements: " + children
);
}

Expand All @@ -89,8 +91,7 @@ private void _startElement(TagImpl tag) {
}
LOGGER.fine(">");

writeTagsRecursively(tag.getTagsAdded());
tag.getTagsAdded().clear();
writeChildren(tag);
} catch (XMLStreamException e) {
throw new FailedWriting(ERROR_WRITING_TO_OUTPUT_FILE, e);
}
Expand Down Expand Up @@ -118,24 +119,35 @@ private List<UpdatingVisitor> getVisitors(String tagName) {

/**
* Write now tags to output stream.
*
* <p>
* This is implemented through call XUnitFixer's handler methods, so Fixup impls are aware of newly added tags.
*/
private void writeTagsRecursively(List<TagImpl> tagsAdded) {
if (tagsAdded.isEmpty()) return;
private boolean writeChildren(TagImpl tag) {
List<Element> childElements = tag.consumeChildren();
if (childElements.isEmpty()) return false;

TagImpl oldCurrentTag = currentTag;
for (TagImpl tag: tagsAdded) {

currentTag = tag;
_startElement(currentTag);

for (TagImpl child: currentTag.getTagsAdded()) {
writeTagsRecursively(List.of(child));
for (Element elements: childElements) {

if (elements instanceof TagImpl) {
currentTag = (TagImpl) elements;
_startElement(currentTag);

writeChildren(currentTag);
endElement(null, null, currentTag.getName());
} else if (elements instanceof Element.SelfWriting) {
Element.SelfWriting sw = (Element.SelfWriting) elements;
try {
sw.write(writer);
} catch(XMLStreamException ex) {
throw new FailedWriting(ERROR_WRITING_TO_OUTPUT_FILE, ex);
}
} else {
throw new AssertionError("Unknown Element implementation found: " + elements.getClass());
}
endElement(null, null, currentTag.getName());
}
currentTag = oldCurrentTag;
return true;
}

@Override
Expand All @@ -156,8 +168,7 @@ public void endElement(String uri, String localName, String tagname) {
visitors.get(i).endTag(tag);
}

writeTagsRecursively(tag.getTagsAdded());
tag.getTagsAdded().clear();
writeChildren(tag);

try {
LOGGER.fine("</" + tagname + ">");
Expand All @@ -176,14 +187,30 @@ public void endElement(String uri, String localName, String tagname) {
}

@Override
public void characters(char[] ch, int start, int length) {
public void characters(char[] orig, int start, int length) {
TagImpl tag = currentTag;

if (tag != null && !tag.isCharactersOmitted()) {
try {
writer.writeCharacters(ch, start, length);
currentChars.update(orig, start, length);
for (UpdatingVisitor visitor : getVisitors(tag.getName())) {
visitor.chars(tag, currentChars);
}

boolean written = writeChildren(tag);
if (!currentChars.isEmpty()) {
if (written) {
throw new IllegalStateException(
"Unable to write characters and children at the same time. "
+ "Make sure to call CharChunk#clear() when elements added in UpdatingVisitor#chars()"
);
}
currentChars.write(writer);
}
} catch (XMLStreamException e) {
throw new FailedWriting(ERROR_WRITING_TO_OUTPUT_FILE, e);
} finally {
currentChars.clear();
}
}
}
Expand Down
Loading

0 comments on commit 13bd7a2

Please sign in to comment.