diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java new file mode 100644 index 00000000000..0016e5e4510 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -0,0 +1,447 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoUserDefinedProperty; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Backend52, Codec52 and OODataModel.JabRef52 refer to the mode of storage, encoding and + * what-is-stored in the document under JabRef version 5.2. These basically did not change up to + * JabRef 5.4. + */ +public class Backend52 { + private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); + public final OODataModel dataModel; + private final NamedRangeManager citationStorageManager; + private final Map cgidToNamedRange; + + // uses: Codec52 + public Backend52() { + this.dataModel = OODataModel.JabRef52; + this.citationStorageManager = new NamedRangeManagerReferenceMark(); + this.cgidToNamedRange = new HashMap<>(); + } + + /** + * Get reference mark names from the document matching the pattern + * used for JabRef reference mark names. + * + * Note: the names returned are in arbitrary order. + * + */ + public List getJabRefReferenceMarkNames(XTextDocument doc) + throws + NoDocumentException { + List allNames = this.citationStorageManager.getUsedNames(doc); + return Codec52.filterIsJabRefReferenceMarkName(allNames); + } + + /** + * Names of custom properties belonging to us, but without a corresponding reference mark. + * These can be deleted. + * + * @param citationGroupNames These are the names that are used. + * + */ + private List findUnusedJabrefPropertyNames(XTextDocument doc, + List citationGroupNames) { + + Set citationGroupNamesSet = new HashSet<>(citationGroupNames); + + List pageInfoThrash = new ArrayList<>(); + List jabrefPropertyNames = + UnoUserDefinedProperty.getListOfNames(doc) + .stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList()); + for (String pn : jabrefPropertyNames) { + if (!citationGroupNamesSet.contains(pn)) { + pageInfoThrash.add(pn); + } + } + return pageInfoThrash; + } + + /** + * @return Optional.empty if all is OK, message text otherwise. + */ + public Optional healthReport(XTextDocument doc) + throws + NoDocumentException { + List pageInfoThrash = + this.findUnusedJabrefPropertyNames(doc, this.getJabRefReferenceMarkNames(doc)); + if (pageInfoThrash.isEmpty()) { + return Optional.empty(); + } + + StringBuilder msg = new StringBuilder("Backend52: found unused pageInfo data, with names listed below.\n"); + msg.append("In LibreOffice you may remove these in [File]/[Properties]/[Custom Properties]\n"); + msg.append(String.join("\n", pageInfoThrash)); + return Optional.of(msg.toString()); + } + + private static void setPageInfoInDataInitial(List citations, Optional pageInfo) { + // attribute to last citation (initially localOrder == storageOrder) + if (!citations.isEmpty()) { + citations.get(citations.size() - 1).setPageInfo(pageInfo); + } + } + + private static Optional getPageInfoFromData(CitationGroup group) { + List citations = group.getCitationsInLocalOrder(); + if (citations.isEmpty()) { + return Optional.empty(); + } + return citations.get(citations.size() - 1).getPageInfo(); + } + + /** + * @param markName Reference mark name + */ + public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, String markName) + throws + WrappedTargetException, + NoDocumentException { + + Codec52.ParsedMarkName parsed = Codec52.parseMarkName(markName).orElseThrow(IllegalArgumentException::new); + + List citations = (parsed.citationKeys.stream() + .map(Citation::new) + .collect(Collectors.toList())); + + Optional pageInfo = (UnoUserDefinedProperty.getStringValue(doc, markName) + .map(OOText::fromString)); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + + setPageInfoInDataInitial(citations, pageInfo); + + NamedRange namedRange = (citationStorageManager.getNamedRangeFromDocument(doc, markName) + .orElseThrow(IllegalArgumentException::new)); + + CitationGroupId groupId = new CitationGroupId(markName); + CitationGroup group = new CitationGroup(OODataModel.JabRef52, + groupId, + parsed.citationType, + citations, + Optional.of(markName)); + this.cgidToNamedRange.put(groupId, namedRange); + return group; + } + + /** + * Create a reference mark at the end of {@code position} in the document. + * + * On return {@code position} is collapsed, and is after the inserted space, or at the end of + * the reference mark. + * + * @param citationKeys Keys to be cited. + * + * @param pageInfos An optional pageInfo for each citation key. + * Backend52 only uses and stores the last pageInfo, + * all others should be Optional.empty() + * + * @param position Collapsed to its end. + * + * @param insertSpaceAfter We insert a space after the mark, that carries on format of + * characters from the original position. + */ + public CitationGroup createCitationGroup(XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + XTextCursor position, + boolean insertSpaceAfter) + throws + CreationException, + NoDocumentException, + WrappedTargetException, + NotRemoveableException, + PropertyVetoException, + IllegalTypeException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new IllegalArgumentException(); + } + + /* + * Backend52 uses reference marks to (1) mark the location of the citation in the text and (2) to encode + * the citation keys and citation type in the name of the reference mark. The name of the reference mark + * has to be unique in the document. + */ + String markName = Codec52.getUniqueMarkName(new HashSet<>(citationStorageManager.getUsedNames(doc)), + citationKeys, + citationType); + + CitationGroupId groupId = new CitationGroupId(markName); + + final int numberOfCitations = citationKeys.size(); + final int last = numberOfCitations - 1; + + // Build citations, add pageInfo to each citation + List citations = new ArrayList<>(numberOfCitations); + for (int i = 0; i < numberOfCitations; i++) { + Citation cit = new Citation(citationKeys.get(i)); + citations.add(cit); + + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(i)); + switch (dataModel) { + case JabRef52: + if (i == last) { + cit.setPageInfo(pageInfo); + } else { + if (pageInfo.isPresent()) { + LOGGER.warn("dataModel JabRef52" + + " only supports pageInfo for the last citation of a group"); + } + } + break; + case JabRef60: + cit.setPageInfo(pageInfo); + break; + default: + throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); + } + } + + /* + * Apply to document + */ + boolean withoutBrackets = (citationType == CitationType.INVISIBLE_CIT); + NamedRange namedRange = this.citationStorageManager.createNamedRange(doc, + markName, + position, + insertSpaceAfter, + withoutBrackets); + + switch (dataModel) { + case JabRef52: + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(last)); + + if (pageInfo.isPresent()) { + String pageInfoString = OOText.toString(pageInfo.get()); + UnoUserDefinedProperty.setStringProperty(doc, markName, pageInfoString); + } else { + // do not inherit from trash + UnoUserDefinedProperty.removeIfExists(doc, markName); + } + CitationGroup group = new CitationGroup(OODataModel.JabRef52, + groupId, + citationType, citations, + Optional.of(markName)); + this.cgidToNamedRange.put(groupId, namedRange); + return group; + case JabRef60: + throw new IllegalStateException("createCitationGroup for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); + } + } + + /** + * @return A list with a nullable pageInfo entry for each citation in joinableGroups. + * + * TODO: JabRef52 combinePageInfos is not reversible. Should warn user to check the result. Or + * ask what to do. + */ + public static List> + combinePageInfosCommon(OODataModel dataModel, List joinableGroup) { + switch (dataModel) { + case JabRef52: + // collect to pageInfos + List> pageInfos = OOListUtil.map(joinableGroup, + Backend52::getPageInfoFromData); + + // Try to do something of the pageInfos. + String singlePageInfo = (pageInfos.stream() + .filter(Optional::isPresent) + .map(pi -> OOText.toString(pi.get())) + .distinct() + .collect(Collectors.joining("; "))); + + int totalCitations = (joinableGroup.stream() + .map(CitationGroup::numberOfCitations) + .mapToInt(Integer::intValue).sum()); + if ("".equals(singlePageInfo)) { + singlePageInfo = null; + } + return OODataModel.fakePageInfos(singlePageInfo, totalCitations); + + case JabRef60: + return (joinableGroup.stream() + .flatMap(group -> (group.citationsInStorageOrder.stream() + .map(Citation::getPageInfo))) + .collect(Collectors.toList())); + default: + throw new IllegalArgumentException("unhandled dataModel here"); + } + } + + /** + * + */ + public List> combinePageInfos(List joinableGroup) { + return combinePageInfosCommon(this.dataModel, joinableGroup); + } + + private NamedRange getNamedRangeOrThrow(CitationGroup group) { + NamedRange namedRange = this.cgidToNamedRange.get(group.groupId); + if (namedRange == null) { + throw new IllegalStateException("getNamedRange: could not lookup namedRange"); + } + return namedRange; + } + + public void removeCitationGroup(CitationGroup group, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NotRemoveableException { + NamedRange namedRange = getNamedRangeOrThrow(group); + String refMarkName = namedRange.getRangeName(); + namedRange.removeFromDocument(doc); + UnoUserDefinedProperty.removeIfExists(doc, refMarkName); + this.cgidToNamedRange.remove(group.groupId); + } + + /** + * @return Optional.empty if the reference mark is missing. + */ + public Optional getMarkRange(CitationGroup group, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getMarkRange(doc); + } + + /** + * Cursor for the reference marks as is: not prepared for filling, but does not need + * cleanFillCursorForCitationGroup either. + */ + public Optional getRawCursorForCitationGroup(CitationGroup group, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getRawCursor(doc); + } + + /** + * Must be followed by call to cleanFillCursorForCitationGroup + */ + public XTextCursor getFillCursorForCitationGroup(CitationGroup group, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getFillCursor(doc); + } + + /** To be called after getFillCursorForCitationGroup */ + public void cleanFillCursorForCitationGroup(CitationGroup group, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + NamedRange namedRange = getNamedRangeOrThrow(group); + namedRange.cleanFillCursor(doc); + } + + public List getCitationEntries(XTextDocument doc, CitationGroups cgs) + throws + WrappedTargetException, + NoDocumentException { + + switch (dataModel) { + case JabRef52: + // One context per CitationGroup: Backend52 (DataModel.JabRef52) + // For DataModel.JabRef60 (Backend60) we need one context per Citation + List citations = new ArrayList<>(cgs.numberOfCitationGroups()); + for (CitationGroup group : cgs.getCitationGroupsUnordered()) { + String name = group.groupId.citationGroupIdAsString(); + XTextCursor cursor = (this + .getRawCursorForCitationGroup(group, doc) + .orElseThrow(IllegalStateException::new)); + String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); + Optional pageInfo = (group.numberOfCitations() > 0 + ? (getPageInfoFromData(group) + .map(e -> OOText.toString(e))) + : Optional.empty()); + CitationEntry entry = new CitationEntry(name, context, pageInfo); + citations.add(entry); + } + return citations; + case JabRef60: + // xx + throw new IllegalStateException("getCitationEntries for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("getCitationEntries: unhandled dataModel "); + } + } + + /* + * Only applies to storage. Citation markers are not changed. + */ + public void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + PropertyVetoException, + IllegalTypeException, + IllegalArgumentException, + WrappedTargetException { + + switch (dataModel) { + case JabRef52: + for (CitationEntry entry : citationEntries) { + Optional pageInfo = entry.getPageInfo().map(OOText::fromString); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + if (pageInfo.isPresent()) { + String name = entry.getRefMarkName(); + UnoUserDefinedProperty.setStringProperty(doc, name, pageInfo.get().toString()); + } + } + break; + case JabRef60: + // xx + throw new IllegalStateException("applyCitationEntries for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("applyCitationEntries: unhandled dataModel "); + } + } + +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java new file mode 100644 index 00000000000..f567a3e459e --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -0,0 +1,137 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.style.CitationType; + +/** + * How and what is encoded in reference mark names under JabRef 5.2. + * + * - pageInfo does not appear here. It is not encoded in the mark name. + */ +class Codec52 { + private static final String BIB_CITATION = "JR_cite"; + private static final Pattern CITE_PATTERN = + // Pattern.compile(BIB_CITATION + "(\\d*)_(\\d*)_(.*)"); + // citationType is always "1" "2" or "3" + Pattern.compile(BIB_CITATION + "(\\d*)_([123])_(.*)"); + + private Codec52() { + /**/ + } + + /** + * This is what we get back from parsing a refMarkName. + */ + public static class ParsedMarkName { + /** "", "0", "1" ... */ + public final String i; + /** in-text-citation type */ + public final CitationType citationType; + /** Citation keys embedded in the reference mark. */ + public final List citationKeys; + + ParsedMarkName(String i, CitationType citationType, List citationKeys) { + Objects.requireNonNull(i); + Objects.requireNonNull(citationKeys); + this.i = i; + this.citationType = citationType; + this.citationKeys = citationKeys; + } + } + + /** + * Integer representation was written into the document in JabRef52, keep it for compatibility. + */ + private static CitationType citationTypeFromInt(int i) { + return switch (i) { + case 1 -> CitationType.AUTHORYEAR_PAR; + case 2 -> CitationType.AUTHORYEAR_INTEXT; + case 3 -> CitationType.INVISIBLE_CIT; + default -> throw new IllegalArgumentException("Invalid CitationType code"); + }; + } + + private static int citationTypeToInt(CitationType i) { + return switch (i) { + case AUTHORYEAR_PAR -> 1; + case AUTHORYEAR_INTEXT -> 2; + case INVISIBLE_CIT -> 3; + default -> throw new IllegalArgumentException("Invalid CitationType"); + }; + } + + /** + * Produce a reference mark name for JabRef for the given citationType and list citation keys that + * does not yet appear among the reference marks of the document. + * + * @param usedNames Reference mark names already in use. + * @param citationKeys Identifies the cited sources. + * @param citationType Encodes the effect of withText and inParenthesis options. + * + * The first occurrence of citationKeys gets no serial number, the second gets 0, the third 1 ... + * + * Or the first unused in this series, after removals. + */ + public static String getUniqueMarkName(Set usedNames, + List citationKeys, + CitationType citationType) { + + String citationKeysPart = String.join(",", citationKeys); + + int i = 0; + int citTypeCode = citationTypeToInt(citationType); + String name = BIB_CITATION + '_' + citTypeCode + '_' + citationKeysPart; + while (usedNames.contains(name)) { + name = BIB_CITATION + i + '_' + citTypeCode + '_' + citationKeysPart; + i++; + } + return name; + } + + /** + * Parse a JabRef (reference) mark name. + * + * @return Optional.empty() on failure. + * + */ + public static Optional parseMarkName(String refMarkName) { + + Matcher citeMatcher = CITE_PATTERN.matcher(refMarkName); + if (!citeMatcher.find()) { + return Optional.empty(); + } + + List keys = Arrays.asList(citeMatcher.group(3).split(",")); + String i = citeMatcher.group(1); + int citTypeCode = Integer.parseInt(citeMatcher.group(2)); + CitationType citationType = citationTypeFromInt(citTypeCode); + return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); + } + + /** + * @return true if name matches the pattern used for JabRef + * reference mark names. + */ + public static boolean isJabRefReferenceMarkName(String name) { + return (CITE_PATTERN.matcher(name).find()); + } + + /** + * Filter a list of reference mark names by `isJabRefReferenceMarkName` + * + * @param names The list to be filtered. + */ + public static List filterIsJabRefReferenceMarkName(List names) { + return (names.stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java new file mode 100644 index 00000000000..ffce5b9553e --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java @@ -0,0 +1,81 @@ +package org.jabref.logic.openoffice.backend; + +import com.sun.star.text.XTextCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods for processing OO Writer documents. + */ +public class GetContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(GetContext.class); + + private GetContext() { + // Just to hide the public constructor + } + + /** + * Get the text belonging to cursor with up to + * charBefore and charAfter characters of context. + * + * The actual context may be smaller than requested. + * + * @param cursor + * @param charBefore Number of characters requested. + * @param charAfter Number of characters requested. + * @param htmlMarkup If true, the text belonging to the reference mark is surrounded by bold + * html tag. + * + */ + public static String getCursorStringWithContext(XTextCursor cursor, + int charBefore, + int charAfter, + boolean htmlMarkup) { + + String citPart = cursor.getString(); + + // extend cursor range left + int flex = 8; + for (int i = 0; i < charBefore; i++) { + try { + cursor.goLeft((short) 1, true); + // If we are close to charBefore and see a space, then cut here. Might avoid cutting + // a word in half. + if ((i >= (charBefore - flex)) + && Character.isWhitespace(cursor.getString().charAt(0))) { + break; + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going left", ex); + } + } + + int lengthWithBefore = cursor.getString().length(); + int addedBefore = lengthWithBefore - citPart.length(); + + cursor.collapseToStart(); + for (int i = 0; i < (charAfter + lengthWithBefore); i++) { + try { + cursor.goRight((short) 1, true); + if (i >= ((charAfter + lengthWithBefore) - flex)) { + String strNow = cursor.getString(); + if (Character.isWhitespace(strNow.charAt(strNow.length() - 1))) { + break; + } + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going right", ex); + } + } + + String result = cursor.getString(); + if (htmlMarkup) { + result = (result.substring(0, addedBefore) + + "" + citPart + "" + + result.substring(lengthWithBefore)); + } + return result.trim(); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java new file mode 100644 index 00000000000..93ad6d12908 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java @@ -0,0 +1,46 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public class NamedRangeManagerReferenceMark implements NamedRangeManager { + + @Override + public NamedRange createNamedRange(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + return NamedRangeReferenceMark.create(doc, refMarkName, position, insertSpaceAfter, withoutBrackets); + } + + @Override + public List getUsedNames(XTextDocument doc) + throws + NoDocumentException { + return UnoReferenceMark.getListOfNames(doc); + } + + @Override + public Optional getNamedRangeFromDocument(XTextDocument doc, String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (NamedRangeReferenceMark + .getFromDocument(doc, refMarkName) + .map(x -> x)); + } +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java new file mode 100644 index 00000000000..cfabb1e2de0 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -0,0 +1,447 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XText; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// was StorageBaseRefMark + +class NamedRangeReferenceMark implements NamedRange { + + private static final String ZERO_WIDTH_SPACE = "\u200b"; + + // for debugging we may want visible bracket + private static final boolean + REFERENCE_MARK_USE_INVISIBLE_BRACKETS = true; // !debug; + + public static final String + REFERENCE_MARK_LEFT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : "<"; + + public static final String + REFERENCE_MARK_RIGHT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : ">"; + + private static final Logger LOGGER = LoggerFactory.getLogger(NamedRangeReferenceMark.class); + + private String id; /* reference mark name */ + + private NamedRangeReferenceMark(String id) { + this.id = id; + } + + String getId() { + return id; + } + + /** + * Insert {@code n} spaces in a way that reference marks just before or just after the cursor + * are not affected. + * + * This is based on the observation, that starting two new paragraphs separates us from + * reference marks on either side. + * + * The pattern used is: + * {@code safeInsertSpaces(n): para, para, left, space(n), right-delete, left(n), left-delete} + * + * @param position Where to insert (at position.getStart()) + * @param numSpaces Number of spaces to insert. + * + * @return a new cursor, covering the just-inserted spaces. + * + */ + private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int numSpaces) { + // Start with an empty cursor at position.getStart(); + XText text = position.getText(); + XTextCursor cursor = text.createTextCursorByRange(position.getStart()); + text.insertString(cursor, "\r\r", false); // para, para + cursor.goLeft((short) 1, false); // left + text.insertString(cursor, " ".repeat(numSpaces), false); // space(numSpaces) + cursor.goRight((short) 1, true); + cursor.setString(""); // right-delete + cursor.goLeft((short) numSpaces, false); // left(numSpaces) + cursor.goLeft((short) 1, true); + cursor.setString(""); // left-delete + cursor.goRight((short) numSpaces, true); // select the newly inserted spaces + return cursor; + } + + private static void createReprInDocument(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + // The cursor we received: we push it before us. + position.collapseToEnd(); + + XTextCursor cursor = safeInsertSpacesBetweenReferenceMarks(position.getEnd(), 2); + + // cursors before the first and after the last space + XTextCursor cursorBefore = cursor.getText().createTextCursorByRange(cursor.getStart()); + XTextCursor cursorAfter = cursor.getText().createTextCursorByRange(cursor.getEnd()); + + cursor.collapseToStart(); + cursor.goRight((short) 1, false); + // now we are between two spaces + + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + String bracketedContent = (withoutBrackets + ? "" + : left + right); + + cursor.getText().insertString(cursor, bracketedContent, true); + + UnoReferenceMark.create(doc, refMarkName, cursor, true /* absorb */); + + // eat the first inserted space + cursorBefore.goRight((short) 1, true); + cursorBefore.setString(""); + if (!insertSpaceAfter) { + // eat the second inserted space + cursorAfter.goLeft((short) 1, true); + cursorAfter.setString(""); + } + } + + static NamedRangeReferenceMark create(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + createReprInDocument(doc, refMarkName, position, insertSpaceAfter, withoutBrackets); + return new NamedRangeReferenceMark(refMarkName); + } + + /** + * @return Optional.empty if there is no corresponding range. + */ + static Optional getFromDocument(XTextDocument doc, String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (UnoReferenceMark.getAnchor(doc, refMarkName) + .map(e -> new NamedRangeReferenceMark(refMarkName))); + } + + /** + * Remove it from the document. + * + * See: removeCitationGroups + */ + @Override + public void removeFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException { + UnoReferenceMark.removeIfExists(doc, this.getRangeName()); + } + + @Override + public String getRangeName() { + return id; + } + + /** + * + * @return Optional.empty if the reference mark is missing. + * + * See: UnoReferenceMark.getAnchor + */ + @Override + public Optional getMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + String name = this.getRangeName(); + return UnoReferenceMark.getAnchor(doc, name); + } + + /** + * Cursor for the reference marks as is, not prepared for filling, but does not need + * cleanFillCursor either. + * + * @return Optional.empty() if reference mark is missing from the document, + * otherwise an XTextCursor for getMarkRange + * + * See: getRawCursorForCitationGroup + */ + @Override + public Optional getRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + String name = this.getRangeName(); + + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + LOGGER.warn("getRawCursor: markAsTextContent({}).isEmpty()", name); + } + + Optional full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); + if (full.isEmpty()) { + LOGGER.warn("getRawCursor: full.isEmpty()"); + return Optional.empty(); + } + return full; + } + + /** + * See: getFillCursorForCitationGroup + */ + @Override + public XTextCursor getFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + String name = this.getRangeName(); + + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + final boolean debugThisFun = false; + + XTextCursor full = null; + String fullText = null; + for (int i = 1; i <= 2; i++) { + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + String msg = String.format("getFillCursor: markAsTextContent(%s).isEmpty (attempt %d)", name, i); + throw new IllegalStateException(msg); + } + + full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()).orElse(null); + if (full == null) { + String msg = String.format("getFillCursor: full == null (attempt %d)", i); + throw new IllegalStateException(msg); + } + + fullText = full.getString(); + + LOGGER.debug("getFillCursor: fulltext = '{}'", fullText); + + if (fullText.length() >= 2) { + LOGGER.debug("getFillCursor: (attempt: {}) fulltext.length() >= 2, break loop%n", i); + break; + } else { + // (fullText.length() < 2) + if (i == 2) { + String msg = String.format("getFillCursor: (fullText.length() < 2) (attempt %d)", i); + throw new IllegalStateException(msg); + } + // too short, recreate + LOGGER.warn("getFillCursor: too short, recreate"); + + full.setString(""); + UnoReferenceMark.removeIfExists(doc, name); + + final boolean insertSpaceAfter = false; + final boolean withoutBrackets = false; + createReprInDocument(doc, name, full, insertSpaceAfter, withoutBrackets); + } + } + + if (full == null) { + throw new IllegalStateException("getFillCursorFor: full == null (after loop)"); + } + if (fullText == null) { + throw new IllegalStateException("getFillCursor: fullText == null (after loop)"); + } + + fullText = full.getString(); + if (fullText.length() < 2) { + throw new IllegalStateException("getFillCursor: fullText.length() < 2 (after loop)'%n"); + } + XTextCursor beta = full.getText().createTextCursorByRange(full); + beta.collapseToStart(); + beta.goRight((short) 1, false); + beta.goRight((short) (fullText.length() - 2), true); + LOGGER.debug("getFillCursor: beta(1) covers '{}'", beta.getString()); + + final short rightLength = (short) right.length(); + if (fullText.startsWith(left) && fullText.endsWith(right)) { + beta.setString(""); + } else { + LOGGER.debug("getFillCursor: recreating brackets for '{}'", fullText); + + // we have at least two characters inside + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + + // beta now covers everything except first and last character + // Replace its content with brackets + String paddingx = "x"; + String paddingy = "y"; + String paddingz = "z"; + beta.setString(paddingx + left + paddingy + right + paddingz); + LOGGER.debug("getFillCursor: beta(2) covers '{}'", beta.getString()); + + // move beta to before the right bracket + beta.collapseToEnd(); + beta.goLeft((short) (rightLength + 1), false); + // remove middle padding + beta.goLeft((short) 1, true); + LOGGER.debug("getFillCursor: beta(3) covers '{}'", beta.getString()); + + // only drop paddingy later: beta.setString(""); + + // drop the initial character and paddingx + alpha.collapseToStart(); + alpha.goRight((short) (1 + 1), true); + LOGGER.debug("getFillCursor: alpha(4) covers '{}'", alpha.getString()); + + alpha.setString(""); + // drop the last character and paddingz + omega.collapseToEnd(); + omega.goLeft((short) (1 + 1), true); + LOGGER.debug("getFillCursor: omega(5) covers '{}'", omega.getString()); + + omega.setString(""); + + // drop paddingy now + LOGGER.debug("getFillCursor: beta(6) covers '{}'", beta.getString()); + + beta.setString(""); + // should be OK now. + if (debugThisFun) { + final short leftLength = (short) left.length(); + alpha.goRight(leftLength, true); + LOGGER.debug("getFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); + omega.goLeft(rightLength, true); + LOGGER.debug("getFillCursor: omega(8) covers '{}', should be '{}'", omega.getString(), right); + } + } + + // NamedRangeReferenceMark.checkFillCursor(beta); + return beta; + } + + /* + * Throw IllegalStateException if the brackets are not there. + */ + public static void checkFillCursor(XTextCursor cursor) { + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + + XTextCursor alpha = cursor.getText().createTextCursorByRange(cursor); + alpha.collapseToStart(); + + XTextCursor omega = cursor.getText().createTextCursorByRange(cursor); + omega.collapseToEnd(); + + final short leftLength = (short) left.length(); + if (leftLength > 0) { + alpha.goLeft(leftLength, true); + if (!left.equals(alpha.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not prefixed with REFERENCE_MARK_LEFT_BRACKET, has '%s'", + cursor.getString(), alpha.getString()); + throw new IllegalStateException(msg); + } + } + + final short rightLength = (short) right.length(); + if (rightLength > 0) { + omega.goRight(rightLength, true); + if (!right.equals(omega.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not followed by REFERENCE_MARK_RIGHT_BRACKET, has '%s'", + cursor.getString(), omega.getString()); + throw new IllegalStateException(msg); + } + } + } + + /** + * Remove brackets, but if the result would become empty, leave them; if the result would be a + * single characer, leave the left bracket. + * + * See: cleanFillCursorForCitationGroup + */ + @Override + public void cleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + // alwaysRemoveBrackets : full compatibility with JabRef 5.2: brackets are temporary, only + // exist between getFillCursor and cleanFillCursor. + final boolean alwaysRemoveBrackets = false; + + // removeBracketsFromEmpty is intended to force removal if we are working on an "Empty citation" (INVISIBLE_CIT). + final boolean removeBracketsFromEmpty = false; + + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + String name = this.getRangeName(); + + XTextCursor full = this.getRawCursor(doc).orElseThrow(IllegalStateException::new); + final String fullText = full.getString(); + final int fullTextLength = fullText.length(); + + if (!fullText.startsWith(left)) { + String msg = String.format("cleanFillCursor: (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", name); + throw new IllegalStateException(msg); + } + + if (!fullText.endsWith(right)) { + String msg = String.format("cleanFillCursor: (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", name); + throw new IllegalStateException(msg); + } + + final int contentLength = (fullTextLength - (leftLength + rightLength)); + if (contentLength < 0) { + String msg = String.format("cleanFillCursor: length(%s) < 0", name); + throw new IllegalStateException(msg); + } + + boolean removeRight = ((contentLength >= 1) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + boolean removeLeft = ((contentLength >= 2) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + if (removeRight) { + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + omega.goLeft(rightLength, true); + omega.setString(""); + } + + if (removeLeft) { + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + alpha.goRight(leftLength, true); + alpha.setString(""); + } + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java index 6c22c8abb09..4bc3fac6191 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java @@ -168,12 +168,12 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { List citationGroups = new ArrayList<>(); for (CitationPath p : ck.getCitationPaths()) { - CitationGroupId cgid = p.group; - Optional cg = cgs.getCitationGroup(cgid); - if (cg.isEmpty()) { + CitationGroupId groupId = p.group; + Optional group = cgs.getCitationGroup(groupId); + if (group.isEmpty()) { throw new IllegalStateException(); } - citationGroups.add(cg.get()); + citationGroups.add(group.get()); } // sort the citationGroups according to their indexInGlobalOrder @@ -184,11 +184,11 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { }); int i = 0; - for (CitationGroup cg : citationGroups) { + for (CitationGroup group : citationGroups) { if (i > 0) { sb.append(", "); } - String markName = cg.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); + String markName = group.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); OOText xref = OOFormat.formatReferenceToPageNumberOfReferenceMark(markName); sb.append(xref.toString()); i++; diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index 896e747913f..1aaeb538930 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -64,6 +64,9 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup String citationKey = citedKey.citationKey; List clashingKeys = normCitMarkerToClachingKeys.putIfAbsent(normCitMarker, new ArrayList<>(1)); + if (clashingKeys == null) { + clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); + } if (!clashingKeys.contains(citationKey)) { // First appearance of citationKey, add to list. clashingKeys.add(citationKey); @@ -107,8 +110,8 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup */ private static void setIsFirstAppearanceOfSourceInCitations(CitationGroups cgs) { Set seenBefore = new HashSet<>(); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { - for (Citation cit : cg.getCitationsInLocalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { + for (Citation cit : group.getCitationsInLocalOrder()) { String currentKey = cit.citationKey; if (!seenBefore.contains(currentKey)) { cit.setIsFirstAppearanceOfSource(true); @@ -142,17 +145,17 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { // Mark first appearance of each citationKey setIsFirstAppearanceOfSourceInCitations(cgs); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { - final boolean inParenthesis = (cg.citationType == CitationType.AUTHORYEAR_PAR); + final boolean inParenthesis = (group.citationType == CitationType.AUTHORYEAR_PAR); final NonUniqueCitationMarker strictlyUnique = NonUniqueCitationMarker.THROWS; - List cits = cg.getCitationsInLocalOrder(); + List cits = group.getCitationsInLocalOrder(); List citationMarkerEntries = OOListUtil.map(cits, e -> e); OOText citMarker = style.createCitationMarker(citationMarkerEntries, inParenthesis, strictlyUnique); - cg.setCitationMarker(Optional.of(citMarker)); + group.setCitationMarker(Optional.of(citMarker)); } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java index 9789d6c6caa..5329d9ca021 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java @@ -24,12 +24,12 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { cgs.createPlainBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { String citMarker = style.getCitationGroupMarkupBefore() - + String.join(",", OOListUtil.map(cg.getCitationsInLocalOrder(), Citation::getCitationKey)) + + String.join(",", OOListUtil.map(group.getCitationsInLocalOrder(), Citation::getCitationKey)) + style.getCitationGroupMarkupAfter(); - cg.setCitationMarker(Optional.of(OOText.fromString(citMarker))); + group.setCitationMarker(Optional.of(OOText.fromString(citMarker))); } } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java index 589ebfb7a48..deddab66cca 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java @@ -37,10 +37,10 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { cgs.createNumberedBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); } - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { - List cits = OOListUtil.map(cg.getCitationsInLocalOrder(), e -> e); + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { + List cits = OOListUtil.map(group.getCitationsInLocalOrder(), e -> e); OOText citMarker = style.getNumCitationMarker2(cits); - cg.setCitationMarker(Optional.of(citMarker)); + group.setCitationMarker(Optional.of(citMarker)); } } diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java new file mode 100644 index 00000000000..63981ceba70 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -0,0 +1,71 @@ +package org.jabref.model.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +/** + * NamedRange (with NamedRangeManager) attempts to provide a common interface for working with + * reference mark based and bookmark based text ranges to be used as locations to fill with citation + * markers. LibreOffice supports name-based lookup and listing names for both (hence the name). + * + * Note: currently only implemented for refence marks (in NamedRangeReferenceMark and + * NamedRangeManagerReferenceMark). + * + */ +public interface NamedRange { + + String getRangeName(); + + /** + * @return Optional.empty if the mark is missing from the document. + */ + Optional getMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Cursor for the reference marks as is, not prepared for filling, but does not need + * cleanFillCursor either. + */ + Optional getRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Get a cursor for filling in text. + * + * Must be followed by cleanFillCursor() + */ + XTextCursor getFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException; + + /** + * Remove brackets, but if the result would become empty, leave them; if the result would be a + * single character, leave the left bracket. + * + */ + void cleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Note: create is in NamedRangeManager + */ + void removeFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException; +} diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java new file mode 100644 index 00000000000..9bbcc752350 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java @@ -0,0 +1,31 @@ +package org.jabref.model.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public interface NamedRangeManager { + + NamedRange createNamedRange(XTextDocument doc, + String markName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException; + + List getUsedNames(XTextDocument doc) + throws + NoDocumentException; + + Optional getNamedRangeFromDocument(XTextDocument doc, String markName) + throws + NoDocumentException, + WrappedTargetException; +} diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index 6e61faed147..909b088b5b3 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -189,135 +189,135 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) // Handle tags: switch (tagName) { - case "b": - formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); - expectEnd.push("/" + tagName); - break; - case "i": - case "em": - formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); - expectEnd.push("/" + tagName); - break; - case "smallcaps": - formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); - expectEnd.push("/" + tagName); - break; - case "sup": - formatStack.pushLayer(setSuperScript(formatStack)); - expectEnd.push("/" + tagName); - break; - case "sub": - formatStack.pushLayer(setSubScript(formatStack)); - expectEnd.push("/" + tagName); - break; - case "u": - formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); - expectEnd.push("/" + tagName); - break; - case "s": - formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); - expectEnd.push("/" + tagName); - break; - case "/p": - // nop - break; - case "p": - insertParagraphBreak(text, cursor); - cursor.collapseToEnd(); - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "oo:ParaStyleName": - //

- if (StringUtil.isNullOrEmpty(value)) { - LOGGER.debug(String.format("oo:ParaStyleName inherited")); - } else { - if (setParagraphStyle(cursor, value)) { - // Presumably tested already: - LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); - } + case "b": + formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); + expectEnd.push("/" + tagName); + break; + case "i": + case "em": + formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); + expectEnd.push("/" + tagName); + break; + case "smallcaps": + formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); + expectEnd.push("/" + tagName); + break; + case "sup": + formatStack.pushLayer(setSuperScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "sub": + formatStack.pushLayer(setSubScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "u": + formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "s": + formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "/p": + // nop + break; + case "p": + insertParagraphBreak(text, cursor); + cursor.collapseToEnd(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:ParaStyleName": + //

+ if (StringUtil.isNullOrEmpty(value)) { + LOGGER.debug(String.format("oo:ParaStyleName inherited")); + } else { + if (setParagraphStyle(cursor, value)) { + // Presumably tested already: + LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); + } + } + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; } - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; } - } - break; - case "oo:referenceToPageNumberOfReferenceMark": - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "target": - UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; + break; + case "oo:referenceToPageNumberOfReferenceMark": + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "target": + UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } } - } - break; - case "tt": - // Note: "Example" names a character style in LibreOffice. - formatStack.pushLayer(setCharStyleName("Example")); - expectEnd.push("/" + tagName); - break; - case "span": - List> settings = new ArrayList<>(); - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "oo:CharStyleName": - // - settings.addAll(setCharStyleName(value)); - break; - case "lang": - // - // - settings.addAll(setCharLocale(value)); - break; - case "style": - // HTML-style small-caps - if ("font-variant: small-caps".equals(value)) { - settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); - break; + break; + case "tt": + // Note: "Example" names a character style in LibreOffice. + formatStack.pushLayer(setCharStyleName("Example")); + expectEnd.push("/" + tagName); + break; + case "span": + List> settings = new ArrayList<>(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:CharStyleName": + // + settings.addAll(setCharStyleName(value)); + break; + case "lang": + // + // + settings.addAll(setCharLocale(value)); + break; + case "style": + // HTML-style small-caps + if ("font-variant: small-caps".equals(value)) { + settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); + break; + } + LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", + value, key, tagName)); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; } - LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", - value, key, tagName)); - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; } - } - formatStack.pushLayer(settings); - expectEnd.push("/" + tagName); - break; - case "/b": - case "/i": - case "/em": - case "/tt": - case "/smallcaps": - case "/sup": - case "/sub": - case "/u": - case "/s": - case "/span": - formatStack.popLayer(); - String expected = expectEnd.pop(); - if (!tagName.equals(expected)) { - LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", - expected, - tagName, - currentSubstring)); - } - break; - default: - LOGGER.warn(String.format("ignoring unknown tag '<%s>'", tagName)); - break; + formatStack.pushLayer(settings); + expectEnd.push("/" + tagName); + break; + case "/b": + case "/i": + case "/em": + case "/tt": + case "/smallcaps": + case "/sup": + case "/sub": + case "/u": + case "/s": + case "/span": + formatStack.popLayer(); + String expected = expectEnd.pop(); + if (!tagName.equals(expected)) { + LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", + expected, + tagName, + currentSubstring)); + } + break; + default: + LOGGER.warn(String.format("ignoring unknown tag '<%s>'", tagName)); + break; } piv = tagMatcher.end(); diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java index 3b9021eb73f..90afb872d4f 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java @@ -19,7 +19,7 @@ public class CitationGroup { /* * Identifies this citation group. */ - public final CitationGroupId cgid; + public final CitationGroupId groupId; /* * The core data, stored in the document: @@ -60,12 +60,12 @@ public class CitationGroup { private Optional citationMarker; public CitationGroup(OODataModel dataModel, - CitationGroupId cgid, + CitationGroupId groupId, CitationType citationType, List citationsInStorageOrder, Optional referenceMarkNameForLinking) { this.dataModel = dataModel; - this.cgid = cgid; + this.groupId = groupId; this.citationType = citationType; this.citationsInStorageOrder = Collections.unmodifiableList(citationsInStorageOrder); this.localOrder = OOListUtil.makeIndices(citationsInStorageOrder.size()); diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java index 0bfe4a832d0..b03028b76ef 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -62,12 +62,12 @@ public void distributeToCitations(List where, T value) { for (CitationPath p : where) { - CitationGroup cg = citationGroupsUnordered.get(p.group); - if (cg == null) { + CitationGroup group = citationGroupsUnordered.get(p.group); + if (group == null) { LOGGER.warn("CitationGroups.distributeToCitations: group missing"); continue; } - Citation cit = cg.citationsInStorageOrder.get(p.storageIndexInGroup); + Citation cit = group.citationsInStorageOrder.get(p.storageIndexInGroup); fun.accept(new OOPair<>(cit, value)); } } @@ -96,8 +96,8 @@ public void lookupCitations(List databases) { // right thing. Seems to work. But what we gained from avoiding collect-and-distribute // may be lost in more complicated consistency checking in addPath. // - /// for (CitationGroup cg : getCitationGroupsUnordered()) { - /// for (Citation cit : cg.citationsInStorageOrder) { + /// for (CitationGroup group : getCitationGroupsUnordered()) { + /// for (Citation cit : group.citationsInStorageOrder) { /// cit.lookupInDatabases(databases); /// } /// } @@ -114,7 +114,7 @@ public List getCitationGroupsInGlobalOrder() { if (globalOrder.isEmpty()) { throw new IllegalStateException("getCitationGroupsInGlobalOrder: not ordered yet"); } - return OOListUtil.map(globalOrder.get(), cgid -> citationGroupsUnordered.get(cgid)); + return OOListUtil.map(globalOrder.get(), groupId -> citationGroupsUnordered.get(groupId)); } /** @@ -132,8 +132,8 @@ public void setGlobalOrder(List globalOrder) { // Propagate to each CitationGroup int i = 0; - for (CitationGroupId cgid : globalOrder) { - citationGroupsUnordered.get(cgid).setIndexInGlobalOrder(Optional.of(i)); + for (CitationGroupId groupId : globalOrder) { + citationGroupsUnordered.get(groupId).setIndexInGlobalOrder(Optional.of(i)); i++; } } @@ -146,8 +146,8 @@ public boolean hasGlobalOrder() { * Impose an order for citations within each group. */ public void imposeLocalOrder(Comparator entryComparator) { - for (CitationGroup cg : citationGroupsUnordered.values()) { - cg.imposeLocalOrder(entryComparator); + for (CitationGroup group : citationGroupsUnordered.values()) { + group.imposeLocalOrder(entryComparator); } } @@ -157,11 +157,11 @@ public void imposeLocalOrder(Comparator entryComparator) { */ public CitedKeys getCitedKeysUnordered() { LinkedHashMap res = new LinkedHashMap<>(); - for (CitationGroup cg : citationGroupsUnordered.values()) { + for (CitationGroup group : citationGroupsUnordered.values()) { int storageIndexInGroup = 0; - for (Citation cit : cg.citationsInStorageOrder) { + for (Citation cit : group.citationsInStorageOrder) { String key = cit.citationKey; - CitationPath path = new CitationPath(cg.cgid, storageIndexInGroup); + CitationPath path = new CitationPath(group.groupId, storageIndexInGroup); if (res.containsKey(key)) { res.get(key).addPath(path, cit); } else { @@ -181,11 +181,11 @@ public CitedKeys getCitedKeysSortedInOrderOfAppearance() { throw new IllegalStateException("getSortedCitedKeys: no globalOrder"); } LinkedHashMap res = new LinkedHashMap<>(); - for (CitationGroup cg : getCitationGroupsInGlobalOrder()) { - for (int i : cg.getLocalOrder()) { - Citation cit = cg.citationsInStorageOrder.get(i); + for (CitationGroup group : getCitationGroupsInGlobalOrder()) { + for (int i : group.getLocalOrder()) { + Citation cit = group.citationsInStorageOrder.get(i); String citationKey = cit.citationKey; - CitationPath path = new CitationPath(cg.cgid, i); + CitationPath path = new CitationPath(group.groupId, i); if (res.containsKey(citationKey)) { res.get(citationKey).addPath(path, cit); } else { @@ -257,17 +257,17 @@ public void createNumberedBibliographySortedByComparator(Comparator en * Query by CitationGroupId */ - public Optional getCitationGroup(CitationGroupId cgid) { - CitationGroup cg = citationGroupsUnordered.get(cgid); - return Optional.ofNullable(cg); + public Optional getCitationGroup(CitationGroupId groupId) { + CitationGroup group = citationGroupsUnordered.get(groupId); + return Optional.ofNullable(group); } /* * @return true if all citation groups have referenceMarkNameForLinking */ public boolean citationGroupsProvideReferenceMarkNameForLinking() { - for (CitationGroup cg : citationGroupsUnordered.values()) { - if (cg.getReferenceMarkNameForLinking().isEmpty()) { + for (CitationGroup group : citationGroupsUnordered.values()) { + if (group.getReferenceMarkNameForLinking().isEmpty()) { return false; } } @@ -278,16 +278,16 @@ public boolean citationGroupsProvideReferenceMarkNameForLinking() { * Callbacks. */ - public void afterCreateCitationGroup(CitationGroup cg) { - citationGroupsUnordered.put(cg.cgid, cg); + public void afterCreateCitationGroup(CitationGroup group) { + citationGroupsUnordered.put(group.groupId, group); globalOrder = Optional.empty(); bibliography = Optional.empty(); } - public void afterRemoveCitationGroup(CitationGroup cg) { - citationGroupsUnordered.remove(cg.cgid); - globalOrder.map(l -> l.remove(cg.cgid)); + public void afterRemoveCitationGroup(CitationGroup group) { + citationGroupsUnordered.remove(group.groupId); + globalOrder.map(l -> l.remove(group.groupId)); bibliography = Optional.empty(); } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationType.java b/src/main/java/org/jabref/model/openoffice/style/CitationType.java index 14189e903e3..567996ec56e 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationType.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationType.java @@ -11,8 +11,8 @@ public enum CitationType { public boolean inParenthesis() { return switch (this) { - case AUTHORYEAR_PAR, INVISIBLE_CIT -> true; - case AUTHORYEAR_INTEXT -> false; + case AUTHORYEAR_PAR, INVISIBLE_CIT -> true; + case AUTHORYEAR_INTEXT -> false; }; }