diff --git a/binder-app/src/test/java/net/kemitix/binder/app/InstanceStream.java b/binder-app/src/test/java/net/kemitix/binder/app/InstanceStream.java index c54af85f..bbb1dcc3 100644 --- a/binder-app/src/test/java/net/kemitix/binder/app/InstanceStream.java +++ b/binder-app/src/test/java/net/kemitix/binder/app/InstanceStream.java @@ -1,7 +1,5 @@ package net.kemitix.binder.app; -import org.jetbrains.annotations.NotNull; - import javax.enterprise.inject.Instance; import javax.enterprise.util.TypeLiteral; import java.lang.annotation.Annotation; @@ -46,7 +44,6 @@ public void destroy(T instance) { throw new UnsupportedOperationException(); } - @NotNull @Override public Iterator iterator() { return instances.iterator(); diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacade.java b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacade.java index 905fc808..0e605992 100644 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacade.java +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacade.java @@ -27,7 +27,7 @@ public class DocxFacade private final WordprocessingMLPackage mlPackage; private final ObjectFactory factory = getWmlObjectFactory(); - private final AtomicInteger myFootnoteRef = new AtomicInteger(0); + private final AtomicInteger myFootnoteRef = new AtomicInteger(-1); @Inject public DocxFacade( diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacadeFootnoteMixIn.java b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacadeFootnoteMixIn.java index 63db52eb..335c5031 100644 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacadeFootnoteMixIn.java +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxFacadeFootnoteMixIn.java @@ -1,6 +1,7 @@ package net.kemitix.binder.docx; import lombok.SneakyThrows; +import net.kemitix.binder.spi.ManuscriptFormatException; import org.docx4j.openpackaging.exceptions.InvalidFormatException; import org.docx4j.openpackaging.parts.WordprocessingML.FootnotesPart; import org.docx4j.wml.CTFootnotes; @@ -20,20 +21,26 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import java.util.stream.Stream; public interface DocxFacadeFootnoteMixIn extends DocxFacadeParagraphMixIn { AtomicInteger footnoteRef(); - - default R footnote(String ordinal, List

footnoteBody) { - //TODO add footnote bodies from FootnoteBlockDocxNodeHandler - // this will provide styles footnotes - return footnoteReference(footnoteBody); + + /** + * Creates a Footnote with no body. + * + *

Use {@link #footnoteAddBody(R, Stream)} ()} to add tge body.

+ * @param ordinal the ordinal of the footnote + * @return an R containing the footnote anchor as a subscript + */ + default R footnote(String ordinal) { + return footnoteReference(ordinal); } @SneakyThrows - default R footnoteReference(List

footnoteBody) { + default R footnoteReference(String ordinal) { // in document.xml: // // @@ -45,16 +52,21 @@ default R footnoteReference(List

footnoteBody) { CTFootnotes contents = footnotesPart.getContents(); List footnotes = contents.getFootnote(); CTFtnEdn ctFtnEdn = getNextCtFtnEdn(footnotes); - ctFtnEdn.getContent().addAll(Arrays.asList(footnoteBody(footnoteBody))); +// //ctFtnEdn.getContent().addAll(Arrays.asList(footnoteBody(footnoteBody))); CTFtnEdnRef ctFtnEdnRef = factory().createCTFtnEdnRef(); - ctFtnEdnRef.setId(ctFtnEdn.getId()); + BigInteger id = ctFtnEdn.getId(); + ctFtnEdnRef.setId(id); JAXBElement footnoteReference = factory().createRFootnoteReference(ctFtnEdnRef); - String footnoteOrdinal = ctFtnEdn.getId().toString(); + String footnoteOrdinal = id.toString(); + if (!footnoteOrdinal.equals(ordinal)) { + throw new RuntimeException( + "Footnotes are not in order. Found %s, when expecting %s" + .formatted(ordinal, footnoteOrdinal)); + } R r = r(new Object[]{ - footnoteReference, - t(footnoteOrdinal) + footnoteReference }); RPr rPr = rPr(r); @@ -63,6 +75,29 @@ default R footnoteReference(List

footnoteBody) { return r; } + @SneakyThrows + default R footnoteAddBody(R r, Stream content) { + FootnotesPart footnotesPart = getFootnotesPart(); + CTFootnotes contents = footnotesPart.getContents(); + List footnotes = contents.getFootnote(); + List rContent = r.getContent(); + JAXBElement footnoteReference = (JAXBElement) rContent.get(0); + CTFtnEdnRef ctFtnEdnRef = footnoteReference.getValue(); + BigInteger id = ctFtnEdnRef.getId(); + CTFtnEdn ctFtnEdn = getCtFtnEdnById(footnotes, id); + List ctFtnEdnContent = ctFtnEdn.getContent(); + List paras = Arrays.asList(footnoteBody(content.map(P.class::cast).collect(Collectors.toList()))); + ctFtnEdnContent.addAll(paras); + return r; + } + + default CTFtnEdn getCtFtnEdnById(List footnotes, BigInteger id) { + return footnotes.stream() + .filter(o -> o.getId().equals(id)) + .findFirst() + .orElseThrow(); + } + default CTFtnEdn getNextCtFtnEdn(List footnotes) { var id = BigInteger.valueOf(footnoteRef().getAndIncrement()); return footnotes.stream() @@ -96,7 +131,7 @@ default CTFootnotes initFootnotes() { CTFootnotes ctFootnotes = factory().createCTFootnotes(); List footnotes = ctFootnotes.getFootnote(); - // + // // // // @@ -105,18 +140,32 @@ default CTFootnotes initFootnotes() { // CTFtnEdn separator = getNextCtFtnEdn(footnotes); separator.setType(STFtnEdn.SEPARATOR); - separator.getContent().add(p(r(factory().createRSeparator()))); - -// // -// // -// // -// // -// // -// // -// // -// CTFtnEdn continuation = getNextCtFtnEdn(footnotes); -// continuation.setType(STFtnEdn.CONTINUATION_SEPARATOR); -// continuation.getContent().add(p(r(objectfactory().createRContinuationSeparator()))); + separator.setId(BigInteger.valueOf(-1)); + separator.getContent().add( + p( + r( + factory().createRSeparator() + ) + ) + ); + + // + // + // + // + // + // + // + CTFtnEdn continuation = getNextCtFtnEdn(footnotes); + continuation.setType(STFtnEdn.CONTINUATION_SEPARATOR); + continuation.setId(BigInteger.ZERO); + continuation.getContent().add( + p( + r( + factory().createRContinuationSeparator() + ) + ) + ); return ctFootnotes; } @@ -153,11 +202,12 @@ default Object[] footnoteBody(List

footnoteParas) { RPr rPr = rPr(r); rPr.setRStyle(rStyle("FootnoteCharacters")); - objects.add(r); - objects.addAll( - footnoteParas.stream() - .peek(para -> para.setPPr(pPr) - ).collect(Collectors.toList())); + List

paras = footnoteParas.stream() + .peek(para -> para.setPPr(pPr) + ).collect(Collectors.toList()); + objects.addAll(paras); + P firstP = paras.get(0); + firstP.getContent().add(0, r); return objects.toArray(); } } diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxManuscript.java b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxManuscript.java index e7b93a92..b1ff107b 100644 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/DocxManuscript.java +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/DocxManuscript.java @@ -191,11 +191,19 @@ private Style styleFootnote() { // indent margins PPrBase.Ind ind = factory.createPPrBaseInd(); - ind.setLeft(BigInteger.valueOf(339)); - ind.setRight(BigInteger.valueOf(339)); - ind.setHanging(BigInteger.valueOf(339)); + BigInteger margin = BigInteger.valueOf(400); + ind.setLeft(margin); + ind.setRight(margin); + ind.setHanging(margin); pPr.setInd(ind); + // do not put space between paragraphs of the same style + PPrBase.Spacing spacing = factory.createPPrBaseSpacing(); + spacing.setBefore(BigInteger.ZERO); + spacing.setAfter(BigInteger.valueOf(75)); + pPr.setSpacing(spacing); +// pPr.setContextualSpacing(factory.createBooleanDefaultTrue()); + style.setPPr(pPr); // Font size 10pt (i.e. 20 /2) diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteBlockDocxNodeHandler.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteBlockDocxNodeHandler.java deleted file mode 100644 index 7c73e330..00000000 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteBlockDocxNodeHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.kemitix.binder.docx.mdconvert; - -import net.kemitix.binder.markdown.FootnoteBlockNodeHandler; - -import javax.enterprise.context.ApplicationScoped; - -@Docx -@ApplicationScoped -public class FootnoteBlockDocxNodeHandler - implements FootnoteBlockNodeHandler { - -} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteDocxNodeHandler.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteDocxNodeHandler.java deleted file mode 100644 index 0e791332..00000000 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteDocxNodeHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.kemitix.binder.docx.mdconvert; - -import com.vladsch.flexmark.util.ast.Node; -import net.kemitix.binder.docx.DocxFacade; -import net.kemitix.binder.markdown.FootnoteAnchor; -import net.kemitix.binder.markdown.FootnoteNodeHandler; -import net.kemitix.binder.spi.FootnoteStore; -import org.docx4j.wml.P; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import java.util.List; -import java.util.stream.Stream; - -@Docx -@ApplicationScoped -public class FootnoteDocxNodeHandler - implements FootnoteNodeHandler { - - private final DocxFacade docx; - private final FootnoteStore

footnoteStore; - - @Inject - public FootnoteDocxNodeHandler( - DocxFacade docx, - @Docx FootnoteStore

footnoteStore - ) { - this.docx = docx; - this.footnoteStore = footnoteStore; - } - - @Override - public Class getNodeClass() { - return com.vladsch.flexmark.ext.footnotes.Footnote.class; - } - - @Override - public Stream footnoteBody(FootnoteAnchor footnoteAnchor) { - String oridinal = footnoteAnchor.getOridinal(); - List

paras = footnoteStore.get(footnoteAnchor.getName(), oridinal); - return Stream.of( - docx.footnote(oridinal, paras) - ); - } - -} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteStoreDocxProvider.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteStoreDocxProvider.java deleted file mode 100644 index 00cd0e9f..00000000 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/FootnoteStoreDocxProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.kemitix.binder.docx.mdconvert; - -import net.kemitix.binder.spi.FootnoteStore; -import org.docx4j.wml.P; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; - -@ApplicationScoped -public class FootnoteStoreDocxProvider { - - @Docx - @Produces - public FootnoteStore

footnoteStore() { - return FootnoteStore.create(P.class); - } - -} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/MarkdownDocxConverter.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/MarkdownDocxConverter.java index 1d4bae7a..85cc594a 100644 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/MarkdownDocxConverter.java +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/MarkdownDocxConverter.java @@ -1,13 +1,17 @@ package net.kemitix.binder.docx.mdconvert; import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.util.ast.Document; import lombok.extern.java.Log; +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.markdown.DocumentModifier; import net.kemitix.binder.markdown.MarkdownConverter; import net.kemitix.binder.markdown.NodeHandler; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Instance; import javax.inject.Inject; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @Log @@ -18,14 +22,17 @@ public class MarkdownDocxConverter private final Parser parser; private final Instance> nodeHandlers; + private final Instance documentModifiers; @Inject public MarkdownDocxConverter( Parser parser, - @Docx Instance> nodeHandlers + @Docx Instance> nodeHandlers, + Instance documentModifiers ) { this.parser = parser; this.nodeHandlers = nodeHandlers; + this.documentModifiers = documentModifiers; } @Override @@ -38,4 +45,15 @@ public Stream> getNodeHandlers() { return nodeHandlers.stream(); } + @Override + public Document fixUpDocument( + Document document, + Context context + ) { + var docReference = new AtomicReference<>(document); + documentModifiers.stream() + .forEachOrdered(modifier -> + docReference.getAndUpdate(d -> modifier.apply(d, context))); + return docReference.get(); + } } diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/TextDocxNodeHandler.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/TextDocxNodeHandler.java index d2eec914..6df02b1f 100644 --- a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/TextDocxNodeHandler.java +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/TextDocxNodeHandler.java @@ -1,5 +1,6 @@ package net.kemitix.binder.docx.mdconvert; +import lombok.extern.java.Log; import net.kemitix.binder.docx.DocxFacade; import net.kemitix.binder.markdown.TextNodeHandler; @@ -7,6 +8,7 @@ import javax.inject.Inject; import java.util.stream.Stream; +@Log @Docx @ApplicationScoped public class TextDocxNodeHandler diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnote.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnote.java new file mode 100644 index 00000000..d978ba97 --- /dev/null +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnote.java @@ -0,0 +1,31 @@ +package net.kemitix.binder.docx.mdconvert.footnote; + +import net.kemitix.binder.spi.Footnote; +import org.docx4j.wml.P; +import org.docx4j.wml.R; + +import java.util.List; +import java.util.stream.Stream; + +public interface DocxFootnote + extends Footnote { + static DocxFootnote createDocx(String ordinal, R placeholder, Stream

content) { + Footnote footnote = Footnote.create(ordinal, placeholder, content); + return new DocxFootnote() { + @Override + public String getOrdinal() { + return footnote.getOrdinal(); + } + + @Override + public R getPlaceholder() { + return footnote.getPlaceholder(); + } + + @Override + public List

getContent() { + return footnote.getContent(); + } + }; + } +} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnoteStore.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnoteStore.java new file mode 100644 index 00000000..eae60750 --- /dev/null +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/DocxFootnoteStore.java @@ -0,0 +1,9 @@ +package net.kemitix.binder.docx.mdconvert.footnote; + +import net.kemitix.binder.spi.FootnoteStore; +import org.docx4j.wml.P; +import org.docx4j.wml.R; + +public interface DocxFootnoteStore + extends FootnoteStore { +} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteAnchorDocxNodeHandler.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteAnchorDocxNodeHandler.java new file mode 100644 index 00000000..5c6968cf --- /dev/null +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteAnchorDocxNodeHandler.java @@ -0,0 +1,52 @@ +package net.kemitix.binder.docx.mdconvert.footnote; + +import com.vladsch.flexmark.util.ast.Node; +import net.kemitix.binder.docx.DocxFacade; +import net.kemitix.binder.docx.mdconvert.Docx; +import net.kemitix.binder.markdown.footnote.FootnoteAnchor; +import net.kemitix.binder.markdown.footnote.FootnoteAnchorNodeHandler; +import net.kemitix.binder.spi.Footnote; +import net.kemitix.binder.spi.FootnoteImpl; +import net.kemitix.binder.spi.FootnoteStoreImpl; +import org.docx4j.wml.P; +import org.docx4j.wml.R; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.util.stream.Stream; + +@Docx +@ApplicationScoped +public class FootnoteAnchorDocxNodeHandler + implements FootnoteAnchorNodeHandler { + + private final DocxFacade docx; + private final DocxFootnoteStore footnoteStore; + + @Inject + public FootnoteAnchorDocxNodeHandler( + DocxFacade docx, + DocxFootnoteStore footnoteStore + ) { + this.docx = docx; + this.footnoteStore = footnoteStore; + } + + @Override + public Class getNodeClass() { + return com.vladsch.flexmark.ext.footnotes.Footnote.class; + } + + @Override + public Stream footnoteAnchor(FootnoteAnchor footnoteAnchor) { + String name = footnoteAnchor.getName(); + String ordinal = footnoteAnchor.getOrdinal(); + R anchor = docx.footnote(ordinal); + DocxFootnote footnote = DocxFootnote.createDocx(ordinal, anchor, Stream.empty()); + footnoteStore.add(name, ordinal, footnote); + return Stream.of( + anchor + ); + } + +} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteBodyDocxNodeHandler.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteBodyDocxNodeHandler.java new file mode 100644 index 00000000..f0ef75ab --- /dev/null +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteBodyDocxNodeHandler.java @@ -0,0 +1,56 @@ +package net.kemitix.binder.docx.mdconvert.footnote; + +import net.kemitix.binder.docx.DocxFacade; +import net.kemitix.binder.docx.mdconvert.Docx; +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.markdown.footnote.FootnoteBodyNodeHandler; +import net.kemitix.binder.spi.Footnote; +import org.docx4j.wml.P; +import org.docx4j.wml.R; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import java.util.stream.Stream; + +@Docx +@ApplicationScoped +public class FootnoteBodyDocxNodeHandler + implements FootnoteBodyNodeHandler { + + private final DocxFacade docx; + private final DocxFootnoteStore footnoteStore; + + @Inject + public FootnoteBodyDocxNodeHandler( + DocxFacade docx, + DocxFootnoteStore footnoteStore + ) { + this.docx = docx; + this.footnoteStore = footnoteStore; + } + + @Override + public Stream footnoteBody(String ordinal, Stream content, Context context) { + Footnote footnote = footnoteStore.get(context.getName(), ordinal); + R placeholder = footnote.getPlaceholder(); + docx.footnoteAddBody(placeholder, formatBody(content)); + return Stream.empty(); + } + + // insert a tab into the first run of each paragraph within the footnote body + private Stream formatBody(Stream content) { + return content.peek(o -> { + if (o instanceof P) { + var p = (P) o; + var r = p.getContent() + .stream() + .filter(po -> po instanceof R) + .map(R.class::cast) + .findFirst() + .orElseThrow(); + r.getContent().add(0, docx.tab()); + } + }); + } + +} diff --git a/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteStoreDocxProvider.java b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteStoreDocxProvider.java new file mode 100644 index 00000000..6a03f042 --- /dev/null +++ b/binder-docx/src/main/java/net/kemitix/binder/docx/mdconvert/footnote/FootnoteStoreDocxProvider.java @@ -0,0 +1,39 @@ +package net.kemitix.binder.docx.mdconvert.footnote; + +import net.kemitix.binder.spi.Footnote; +import net.kemitix.binder.spi.FootnoteStore; +import org.docx4j.wml.P; +import org.docx4j.wml.R; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@ApplicationScoped +public class FootnoteStoreDocxProvider { + + FootnoteStore store = FootnoteStore.create(P.class, R.class); + + @Produces + public DocxFootnoteStore footnoteStore() { + return new DocxFootnoteStore() { + @Override + public void add(String name, String ordinal, Footnote footnote) { + store.add(name, ordinal, footnote); + } + + @Override + public Footnote get(String name, String ordinal) { + return store.get(name, ordinal); + } + + @Override + public Stream>> streamByName(String name) { + return store.streamByName(name); + } + }; + } + +} diff --git a/binder-epub/pom.xml b/binder-epub/pom.xml index 541e72d8..728828f0 100644 --- a/binder-epub/pom.xml +++ b/binder-epub/pom.xml @@ -39,6 +39,11 @@ commons-text + + org.jsoup + jsoup + + org.junit.jupiter junit-jupiter diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/EpubWriter.java b/binder-epub/src/main/java/net/kemitix/binder/epub/EpubWriter.java index 59344855..e83fd8e2 100644 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/EpubWriter.java +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/EpubWriter.java @@ -4,10 +4,8 @@ import lombok.SneakyThrows; import lombok.extern.java.Log; import net.kemitix.binder.markdown.MarkdownConversionException; -import net.kemitix.binder.spi.BinderConfig; -import net.kemitix.binder.spi.BinderException; -import net.kemitix.binder.spi.ManuscriptFormatException; -import net.kemitix.binder.spi.ManuscriptWriter; +import net.kemitix.binder.markdown.MarkdownOutputException; +import net.kemitix.binder.spi.*; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -44,6 +42,9 @@ public void write() { } catch (ManuscriptFormatException e) { throw new BinderException(String.format( "Error creating epub file %s", epubFile), e); + } catch (MarkdownOutputException e) { + throw new BinderException(String.format( + "Error creating epub file %s: %s [%s]", epubFile, e.getMessage(), e.getOutput()), e); } log.info("Wrote: " + epubFile); } diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/MarkdownEpubRenderer.java b/binder-epub/src/main/java/net/kemitix/binder/epub/MarkdownEpubRenderer.java index 543fad17..9f64f3e2 100644 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/MarkdownEpubRenderer.java +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/MarkdownEpubRenderer.java @@ -2,7 +2,7 @@ import coza.opencollab.epub.creator.model.Content; import net.kemitix.binder.epub.mdconvert.Epub; -import net.kemitix.binder.epub.mdconvert.FootnoteGenerator; +import net.kemitix.binder.epub.mdconvert.footnote.FootnoteHtmlContentGenerator; import net.kemitix.binder.markdown.Context; import net.kemitix.binder.markdown.MarkdownConverter; import net.kemitix.binder.spi.HtmlSection; @@ -21,14 +21,14 @@ public class MarkdownEpubRenderer implements EpubRenderer { private final MarkdownConverter converter; - private final FootnoteGenerator footnoteGenerator; + private final FootnoteHtmlContentGenerator footnoteHtmlContentGenerator; @Inject public MarkdownEpubRenderer( @Epub MarkdownConverter converter, - FootnoteGenerator footnoteGenerator) { + FootnoteHtmlContentGenerator footnoteHtmlContentGenerator) { this.converter = converter; - this.footnoteGenerator = footnoteGenerator; + this.footnoteHtmlContentGenerator = footnoteHtmlContentGenerator; } @Override @@ -46,7 +46,7 @@ public Stream render(HtmlSection section) { byte[] content = contents.getBytes(StandardCharsets.UTF_8); return Stream.concat( createContent(section, content), - footnoteGenerator.createFootnotes(section) + footnoteHtmlContentGenerator.createFootnotes(section) ); } diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/StoryEpubRenderer.java b/binder-epub/src/main/java/net/kemitix/binder/epub/StoryEpubRenderer.java index 9a2fbba1..b5fa729f 100644 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/StoryEpubRenderer.java +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/StoryEpubRenderer.java @@ -2,7 +2,7 @@ import coza.opencollab.epub.creator.model.Content; import net.kemitix.binder.epub.mdconvert.Epub; -import net.kemitix.binder.epub.mdconvert.FootnoteGenerator; +import net.kemitix.binder.epub.mdconvert.footnote.FootnoteHtmlContentGenerator; import net.kemitix.binder.markdown.Context; import net.kemitix.binder.markdown.MarkdownConverter; import net.kemitix.binder.spi.HtmlSection; @@ -22,14 +22,14 @@ public class StoryEpubRenderer implements EpubRenderer { private final MarkdownConverter converter; - private final FootnoteGenerator footnoteGenerator; + private final FootnoteHtmlContentGenerator footnoteHtmlContentGenerator; @Inject public StoryEpubRenderer( @Epub MarkdownConverter converter, - FootnoteGenerator footnoteGenerator) { + FootnoteHtmlContentGenerator footnoteHtmlContentGenerator) { this.converter = converter; - this.footnoteGenerator = footnoteGenerator; + this.footnoteHtmlContentGenerator = footnoteHtmlContentGenerator; } @Override @@ -53,7 +53,7 @@ public Stream render(HtmlSection section) { .getBytes(StandardCharsets.UTF_8); return Stream.concat( Stream.of(new Content(section.getHref(), content)), - footnoteGenerator.createFootnotes(section) + footnoteHtmlContentGenerator.createFootnotes(section) ); } diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteBlockEpubNodeHandler.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteBlockEpubNodeHandler.java deleted file mode 100644 index 9ee47882..00000000 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteBlockEpubNodeHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.kemitix.binder.epub.mdconvert; - -import net.kemitix.binder.markdown.FootnoteBlockNodeHandler; -import net.kemitix.binder.markdown.FootnoteBody; - -import javax.enterprise.context.ApplicationScoped; -import java.util.stream.Stream; - -@Epub -@ApplicationScoped -public class FootnoteBlockEpubNodeHandler - implements FootnoteBlockNodeHandler, EpubNodeHandler { - @Override - public Stream footnoteBlockBody(FootnoteBody footnoteBody) { - return Stream.empty(); - } -} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteEpubNodeHandler.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteEpubNodeHandler.java deleted file mode 100644 index 5180a903..00000000 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteEpubNodeHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.kemitix.binder.epub.mdconvert; - -import net.kemitix.binder.markdown.FootnoteAnchor; -import net.kemitix.binder.markdown.FootnoteNodeHandler; - -import javax.enterprise.context.ApplicationScoped; -import java.util.stream.Stream; - -@Epub -@ApplicationScoped -public class FootnoteEpubNodeHandler - implements FootnoteNodeHandler, EpubNodeHandler { - - @Override - public Stream footnoteBody(FootnoteAnchor footnoteAnchor) { - String htmlFile = footnoteAnchor.getHtmlFile(); - String oridinal = footnoteAnchor.getOridinal(); - return Stream.of(""" - %1$s """ - .formatted(oridinal, htmlFile) - ); - } -} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteStoreEpubProvider.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteStoreEpubProvider.java deleted file mode 100644 index 12ba7371..00000000 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteStoreEpubProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.kemitix.binder.epub.mdconvert; - -import net.kemitix.binder.spi.FootnoteStore; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; - -@ApplicationScoped -public class FootnoteStoreEpubProvider { - - @Epub - @Produces - public FootnoteStore footnoteStore() { - return FootnoteStore.create(String.class); - } - -} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnote.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnote.java new file mode 100644 index 00000000..2630c8fc --- /dev/null +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnote.java @@ -0,0 +1,8 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.spi.Footnote; + +public interface EpubFootnote + extends Footnote { + +} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnoteStore.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnoteStore.java new file mode 100644 index 00000000..be89a694 --- /dev/null +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/EpubFootnoteStore.java @@ -0,0 +1,7 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.spi.FootnoteStore; + +public interface EpubFootnoteStore + extends FootnoteStore { +} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteAnchorEpubNodeHandler.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteAnchorEpubNodeHandler.java new file mode 100644 index 00000000..0ee4c466 --- /dev/null +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteAnchorEpubNodeHandler.java @@ -0,0 +1,28 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.epub.mdconvert.Epub; +import net.kemitix.binder.epub.mdconvert.EpubNodeHandler; +import net.kemitix.binder.markdown.footnote.FootnoteAnchor; +import net.kemitix.binder.markdown.footnote.FootnoteAnchorNodeHandler; + +import javax.enterprise.context.ApplicationScoped; +import java.util.stream.Stream; + +@Epub +@ApplicationScoped +public class FootnoteAnchorEpubNodeHandler + implements FootnoteAnchorNodeHandler, EpubNodeHandler { + + @Override + public Stream footnoteAnchor(FootnoteAnchor footnoteAnchor) { + String htmlFile = footnoteAnchor.getHtmlFile(); + String ordinal = footnoteAnchor.getOrdinal(); + return Stream.of(""" + %1$s """ + .formatted(ordinal, htmlFile) + ); + } +} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandler.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandler.java new file mode 100644 index 00000000..c617add0 --- /dev/null +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandler.java @@ -0,0 +1,33 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.epub.mdconvert.Epub; +import net.kemitix.binder.epub.mdconvert.EpubNodeHandler; +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.markdown.MarkdownOutputException; +import net.kemitix.binder.markdown.footnote.FootnoteBodyNodeHandler; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; + +import javax.enterprise.context.ApplicationScoped; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Epub +@ApplicationScoped +public class FootnoteBodyEpubNodeHandler + implements FootnoteBodyNodeHandler, EpubNodeHandler { + @Override + public Stream footnoteBody(String ordinal, Stream content, Context context) { + var body = content.collect(Collectors.joining()) + .replaceAll(" ~PARA~ ", "

"); + var element = Jsoup.parseBodyFragment(body).body(); + var children = element.children(); + if (children.first().is("p")) { + var sup = new Element("sup");; + children.first().insertChildren(0, sup.text(ordinal)); + return Stream.of(element.html()); + } else { + throw new MarkdownOutputException("Generated footnote body should start with a paragraph", body); + } + } +} diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteGenerator.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteHtmlContentGenerator.java similarity index 83% rename from binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteGenerator.java rename to binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteHtmlContentGenerator.java index a68ff538..53bcb15f 100644 --- a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/FootnoteGenerator.java +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteHtmlContentGenerator.java @@ -1,9 +1,10 @@ -package net.kemitix.binder.epub.mdconvert; +package net.kemitix.binder.epub.mdconvert.footnote; import coza.opencollab.epub.creator.model.Content; -import net.kemitix.binder.spi.FootnoteStore; +import net.kemitix.binder.epub.mdconvert.Epub; +import net.kemitix.binder.epub.mdconvert.Tuple; +import net.kemitix.binder.spi.FootnoteStoreImpl; import net.kemitix.binder.spi.HtmlSection; -import org.jetbrains.annotations.NotNull; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -14,12 +15,12 @@ import java.util.stream.Stream; @ApplicationScoped -public class FootnoteGenerator { +public class FootnoteHtmlContentGenerator { - private final FootnoteStore footnoteStore; + private final EpubFootnoteStore footnoteStore; @Inject - public FootnoteGenerator(@Epub FootnoteStore footnoteStore) { + public FootnoteHtmlContentGenerator(EpubFootnoteStore footnoteStore) { this.footnoteStore = footnoteStore; } @@ -28,7 +29,6 @@ public Stream createFootnotes(HtmlSection section) { .streamByName(section.getName()) .map(Tuple::of) .map(t -> t.mapFirst(backlink(section.getName()))) - .map(t -> t.mapSecond(l -> (List)l)) .map(t -> t.mapSecond(l -> String.join("", l))) .map(t -> t.mapSecond(footnoteBody(section.getName()))) .map(t -> t.mapSecond(asBytes())) @@ -37,12 +37,10 @@ public Stream createFootnotes(HtmlSection section) { .peek(content -> content.setToc(false)); } - @NotNull private Function asBytes() { return s -> s.getBytes(StandardCharsets.UTF_8); } - @NotNull private Content asContent(Tuple t) { String href = t.getFirst(); byte[] body = t.getSecond(); diff --git a/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteStoreEpubProvider.java b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteStoreEpubProvider.java new file mode 100644 index 00000000..9d69058e --- /dev/null +++ b/binder-epub/src/main/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteStoreEpubProvider.java @@ -0,0 +1,37 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.spi.Footnote; +import net.kemitix.binder.spi.FootnoteStore; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@ApplicationScoped +public class FootnoteStoreEpubProvider { + + FootnoteStore store = FootnoteStore.create(String.class, String.class); + + @Produces + public EpubFootnoteStore footnoteStore() { + return new EpubFootnoteStore() { + @Override + public void add(String name, String ordinal, Footnote footnote) { + store.add(name, ordinal, footnote); + } + + @Override + public Footnote get(String name, String ordinal) { + return store.get(name, ordinal); + } + + @Override + public Stream>> streamByName(String name) { + return store.streamByName(name); + } + }; + } + +} diff --git a/binder-epub/src/test/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandlerTest.java b/binder-epub/src/test/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandlerTest.java new file mode 100644 index 00000000..e666ea88 --- /dev/null +++ b/binder-epub/src/test/java/net/kemitix/binder/epub/mdconvert/footnote/FootnoteBodyEpubNodeHandlerTest.java @@ -0,0 +1,62 @@ +package net.kemitix.binder.epub.mdconvert.footnote; + +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.spi.FootnoteImpl; +import org.assertj.core.api.WithAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +class FootnoteBodyEpubNodeHandlerTest + implements WithAssertions { + + FootnoteBodyEpubNodeHandler sut = new FootnoteBodyEpubNodeHandler(); + Context context = mock(Context.class); + + @BeforeEach + void setUp() { + given(context.getName()).willReturn("test"); + } + + @Test void insertsOrdinalOnSimpleParagraph() { + //given + var content = Stream.of("

body text

"); + //when + var result = sut.footnoteBody("1", content, context).collect(Collectors.joining()); + //then + assertThat(result).isEqualTo("

1body text

"); + } + + @Test void insertsOrdinalOnStyledParagraph() { + //given + var content = Stream.of("

body text

"); + //when + var result = sut.footnoteBody("1", content, context).collect(Collectors.joining()); + //then + assertThat(result).isEqualTo("

1body text

"); + } + + @Test void insertsOrdinalOnFirstSimpleParagraph() { + //given + var content = Stream.of("

body text

para 2

"); + //when + var result = sut.footnoteBody("1", content, context).collect(Collectors.joining()); + //then + assertThat(result).isEqualTo("

1body text

\n

para 2

"); + } + + @Test void insertsOrdinalOnFirstStyledParagraph() { + //given + var content = Stream.of("

body text

para 2

"); + //when + var result = sut.footnoteBody("1", content, context).collect(Collectors.joining()); + //then + assertThat(result).isEqualTo("

1body text

\n

para 2

"); + } + +} \ No newline at end of file diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/DocumentModifier.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/DocumentModifier.java new file mode 100644 index 00000000..878fbd69 --- /dev/null +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/DocumentModifier.java @@ -0,0 +1,8 @@ +package net.kemitix.binder.markdown; + +import com.vladsch.flexmark.util.ast.Document; + +import java.util.function.BiFunction; + +public interface DocumentModifier extends BiFunction { +} diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBody.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBody.java deleted file mode 100644 index 9a1b03d1..00000000 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBody.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.kemitix.binder.markdown; - -import com.vladsch.flexmark.ext.footnotes.FootnoteBlock; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.stream.Stream; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class FootnoteBody { - - private final String oridinal; - private final Stream content; - - public static FootnoteBody create( - FootnoteBlock footnoteBlock, - Stream content - ) { - String oridinal = footnoteBlock.getText().unescape(); - return new FootnoteBody<>(oridinal, content); - } - -} diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownConverter.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownConverter.java index 57c0be25..31c74242 100644 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownConverter.java +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownConverter.java @@ -3,7 +3,6 @@ import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.ast.Document; import com.vladsch.flexmark.util.ast.Node; -import net.kemitix.binder.spi.Section; import java.util.stream.Stream; @@ -14,11 +13,15 @@ public interface MarkdownConverter { Stream> getNodeHandlers(); default Stream convert(Context context, String markdown) { - Document document = getParser().parse(markdown); + Document document = fixUpDocument(getParser().parse(markdown), context); Stream accepted = accept(document, context); return accepted; } + default Document fixUpDocument(Document document, Context context) { + return document; + } + default Stream accept(Node node, Context context) { NodeHandler handler = findHandler(node.getClass()); Stream objects = handler.handle(node, this, context); diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownOutputException.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownOutputException.java new file mode 100644 index 00000000..85781635 --- /dev/null +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/MarkdownOutputException.java @@ -0,0 +1,19 @@ +package net.kemitix.binder.markdown; + +import lombok.Getter; + +@Getter +public class MarkdownOutputException extends RuntimeException { + + private final String output; + + public MarkdownOutputException(String message, String output) { + super(message); + this.output = output; + } + + public MarkdownOutputException(String message, String output, Throwable cause) { + super(message, cause); + this.output = output; + } +} diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/ModifierFootnoteParagraphs.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/ModifierFootnoteParagraphs.java new file mode 100644 index 00000000..2dcf090a --- /dev/null +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/ModifierFootnoteParagraphs.java @@ -0,0 +1,81 @@ +package net.kemitix.binder.markdown; + +import com.vladsch.flexmark.ast.Paragraph; +import com.vladsch.flexmark.ast.SoftLineBreak; +import com.vladsch.flexmark.ast.Text; +import com.vladsch.flexmark.ext.footnotes.FootnoteBlock; +import com.vladsch.flexmark.util.ast.Document; +import com.vladsch.flexmark.util.ast.Node; +import com.vladsch.flexmark.util.ast.NodeVisitor; +import com.vladsch.flexmark.util.ast.VisitHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; + +import javax.enterprise.context.ApplicationScoped; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Log +@ApplicationScoped +public class ModifierFootnoteParagraphs + implements DocumentModifier{ + + NodeVisitor findFootnotes = new NodeVisitor( + new VisitHandler<>(FootnoteBlock.class, this::visitFootnoteBlock) + ); + + @Override + public Document apply(Document document, Context context) { + findFootnotes.visit(document); + return document; + } + + private void visitFootnoteBlock(@NotNull FootnoteBlock footnoteBlock) { + Paragraph originalPara = + Objects.requireNonNull((Paragraph) footnoteBlock.getFirstChild(), "Footnote first child"); + if (originalPara.getChars().toString().contains("~PARA~")) { + List paras = new ReflowParagraphs(originalPara).reflow(); + footnoteBlock.removeChildren(); + paras.forEach(footnoteBlock::appendChild); + } + } + + @RequiredArgsConstructor + private static class ReflowParagraphs { + + private final Paragraph paragraph; + + public List reflow() { + List paras = new ArrayList<>(); + Optional.ofNullable(paragraph.getFirstChild()) + .map(List::of) + .map(this::paraFrom) + .ifPresent(paras::add); + List nextPara = new ArrayList<>(); + paragraph.getChildren().forEach(child -> { + if (child instanceof SoftLineBreak) { + if (!nextPara.isEmpty()) { + paras.add(paraFrom(nextPara)); + } + nextPara.clear(); + } else if (child instanceof Text && ((Text) child).getChars().toString().equals("~PARA~")) { + // skip + } else { + nextPara.add(child); + } + }); + return paras; + } + + private Paragraph paraFrom(List nodes) { + Paragraph p = new Paragraph(); + nodes.forEach(p::appendChild); + return p; + } + } + + +} diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/NodeHandler.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/NodeHandler.java index 68beb572..6d63173f 100644 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/NodeHandler.java +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/NodeHandler.java @@ -2,6 +2,7 @@ import com.vladsch.flexmark.util.ast.Node; +import java.util.Objects; import java.util.stream.Stream; public interface NodeHandler { @@ -19,11 +20,18 @@ default Stream handle( ) { Stream children = handleChildren(node, converter, context); return Stream.concat( - body(node, children, context), - handleNext(node, converter, context) + requireNonNull(body(node, children, context), "body", node), + requireNonNull(handleNext(node, converter, context), "handleNext", node) ); } + default O requireNonNull(O object, String tag, Node node) { + if (Objects.isNull(object)) { + throw new MarkdownOutputException("Element is null: %s [%s]".formatted(tag, node.toString()), node.toString()); + } + return object; + } + default Stream body(Node node, Stream content, Context context) { return content; } diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteAnchor.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchor.java similarity index 84% rename from binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteAnchor.java rename to binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchor.java index ea3eba8d..973a91d6 100644 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteAnchor.java +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchor.java @@ -1,15 +1,16 @@ -package net.kemitix.binder.markdown; +package net.kemitix.binder.markdown.footnote; import com.vladsch.flexmark.ext.footnotes.Footnote; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.kemitix.binder.markdown.Context; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class FootnoteAnchor { private final String name; - private final String oridinal; + private final String ordinal; private final String htmlFile; public static FootnoteAnchor create( diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteNodeHandler.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchorNodeHandler.java similarity index 54% rename from binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteNodeHandler.java rename to binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchorNodeHandler.java index a8da07bb..a92ce222 100644 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteNodeHandler.java +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteAnchorNodeHandler.java @@ -1,12 +1,13 @@ -package net.kemitix.binder.markdown; +package net.kemitix.binder.markdown.footnote; import com.vladsch.flexmark.ext.footnotes.Footnote; import com.vladsch.flexmark.util.ast.Node; -import net.kemitix.binder.spi.Section; +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.markdown.NodeHandler; import java.util.stream.Stream; -public interface FootnoteNodeHandler +public interface FootnoteAnchorNodeHandler extends NodeHandler { default Class getNodeClass() { @@ -19,9 +20,9 @@ default Stream body( Stream content, Context context ) { - return footnoteBody(FootnoteAnchor.create((Footnote) node, context)); + return footnoteAnchor(FootnoteAnchor.create((Footnote) node, context)); } - Stream footnoteBody(FootnoteAnchor footnoteAnchor); + Stream footnoteAnchor(FootnoteAnchor footnoteAnchor); } diff --git a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBlockNodeHandler.java b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteBodyNodeHandler.java similarity index 50% rename from binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBlockNodeHandler.java rename to binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteBodyNodeHandler.java index 3918425a..fbea8022 100644 --- a/binder-markdown/src/main/java/net/kemitix/binder/markdown/FootnoteBlockNodeHandler.java +++ b/binder-markdown/src/main/java/net/kemitix/binder/markdown/footnote/FootnoteBodyNodeHandler.java @@ -1,12 +1,15 @@ -package net.kemitix.binder.markdown; +package net.kemitix.binder.markdown.footnote; import com.vladsch.flexmark.ext.footnotes.FootnoteBlock; import com.vladsch.flexmark.util.ast.Node; -import net.kemitix.binder.spi.Section; +import net.kemitix.binder.markdown.Context; +import net.kemitix.binder.markdown.NodeHandler; +import net.kemitix.binder.spi.Footnote; +import net.kemitix.binder.spi.FootnoteImpl; import java.util.stream.Stream; -public interface FootnoteBlockNodeHandler +public interface FootnoteBodyNodeHandler extends NodeHandler { @Override @@ -17,12 +20,11 @@ default Class getNodeClass() { @Override default Stream body(Node node, Stream content, Context context) { FootnoteBlock footnoteBlock = (FootnoteBlock) node; - FootnoteBody footnoteBody = - FootnoteBody.create(footnoteBlock, content); - return footnoteBlockBody(footnoteBody); + String ordinal = footnoteBlock.getText().unescape(); + return footnoteBody(ordinal, content, context); } - default Stream footnoteBlockBody(FootnoteBody footnoteBody) { + default Stream footnoteBody(String ordinal, Stream content, Context context) { return Stream.empty(); } } diff --git a/binder-parent/pom.xml b/binder-parent/pom.xml index 9f598f2b..40b8bd07 100644 --- a/binder-parent/pom.xml +++ b/binder-parent/pom.xml @@ -32,6 +32,7 @@ 1.0.2 2.2.0 1.9 + 1.11.3 @@ -73,6 +74,12 @@ ${lombok.version}
+ + org.jsoup + jsoup + ${jsoup.version} + + jakarta.platform jakarta.jakartaee-api diff --git a/binder-spi/src/main/java/net/kemitix/binder/spi/Footnote.java b/binder-spi/src/main/java/net/kemitix/binder/spi/Footnote.java new file mode 100644 index 00000000..651df036 --- /dev/null +++ b/binder-spi/src/main/java/net/kemitix/binder/spi/Footnote.java @@ -0,0 +1,23 @@ +package net.kemitix.binder.spi; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public interface Footnote { + + static Footnote create( + String ordinal, + P placeholder, + Stream content + ) { + List collect = content.collect(Collectors.toList()); + return new FootnoteImpl<>(ordinal, placeholder, collect); + } + + String getOrdinal(); + + P getPlaceholder(); + + List getContent(); +} diff --git a/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteImpl.java b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteImpl.java new file mode 100644 index 00000000..6bc013f1 --- /dev/null +++ b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteImpl.java @@ -0,0 +1,20 @@ +package net.kemitix.binder.spi; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class FootnoteImpl + implements Footnote { + + private final String ordinal; + private final P placeholder; + private final List content; + +} diff --git a/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStore.java b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStore.java index 217ff7fe..128ba468 100644 --- a/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStore.java +++ b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStore.java @@ -1,47 +1,21 @@ package net.kemitix.binder.spi; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Stream; -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class FootnoteStore { - - // name -> ordinal -> LIST - private final Map>> stores = new HashMap<>(); +public interface FootnoteStore { - public static FootnoteStore create(Class aClass) { - return new FootnoteStore(); + static FootnoteStore create( + Class typeClass, + Class placeholderClass + ) { + return new FootnoteStoreImpl<>(); } - public void add(String name, String oridinal, List content) { - getStore(name) - .put(oridinal, content); - } + void add(String name, String ordinal, Footnote footnote); - private Map> getStore(String name) { - return stores.computeIfAbsent(name, x -> new HashMap<>()); - } + Footnote get(String name, String ordinal); - public List get(String name, String ordinal) { - return Objects.requireNonNullElseGet( - getStore(name) - .get(ordinal), - Collections::emptyList); - } - - public Stream>> streamByName(String name) { - return getStore(name) - .entrySet() - .stream(); - } + Stream>> streamByName(String name); } diff --git a/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStoreImpl.java b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStoreImpl.java new file mode 100644 index 00000000..1d599702 --- /dev/null +++ b/binder-spi/src/main/java/net/kemitix/binder/spi/FootnoteStoreImpl.java @@ -0,0 +1,46 @@ +package net.kemitix.binder.spi; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public class FootnoteStoreImpl + implements FootnoteStore { + + // name -> ordinal -> placeholder + private final Map>> stores = new HashMap<>(); + + @Override + public void add(String name, String ordinal, Footnote footnote) { + getStore(name) + .put(ordinal, footnote); + } + + private Map> getStore(String name) { + return stores.computeIfAbsent(name, x -> new HashMap<>()); + } + + @Override + public Footnote get(String name, String ordinal) { + return Objects.requireNonNull( + Objects.requireNonNull(getStore(name), "Store for " + name) + .get(ordinal), "Footnote for " + name + " - " + ordinal); + } + + @Override + public Stream>> streamByName(String name) { + return getStore(name) + .entrySet() + .stream() + .map(e -> Map.entry(e.getKey(), e.getValue().getContent())); + } + +} diff --git a/doc/images/reactor-graph.png b/doc/images/reactor-graph.png index e4e430e8..6ae0ec84 100644 Binary files a/doc/images/reactor-graph.png and b/doc/images/reactor-graph.png differ