ContentMatcher

com.phasmidsoftware.xmldoc.merge.ContentMatcher

Matches base against modified using pure content/structural similarity - no identifier needed, unlike TreeMatcher.matchBySelf. Loosely modeled on the real 3dm tool's own matcher (see MERGE.md, "the actual 3dm implementation's matcher"): exact whole-subtree content equality first (a node - and everything below it, byte for byte - is almost certainly the same node even if it moved or its siblings changed), then positional pairing for whatever's left among an already-matched parent's remaining children.

Deliberately simpler than 3dm's own algorithm: no fuzzy content similarity (q-gram distance) for the positional fallback - it just pairs whatever's left, in order - and no real "copy resolution" for duplicate content (see matchByExactContent). It also never looks across an unmatched parent: a node moved to a genuinely different parent shows up as a plain delete-from-the-old- parent plus insert-into-the-new-one, not a recognized move - a real, known scope limit, not a bug.

This exists to fix a real, confirmed problem (MERGE.md, "no stable per-node identity" / "silently lose an edit..."): for a document type with nothing like IDML's Self, Pcs.label's NodePath fallback ties identity to raw position, so a deletion that shifts later siblings can make an unrelated, independent edit to one of them silently vanish from a 3-way merge. Matching by content first, and only falling back to position for the unmatched remainder, fixes exactly that: an edited-but-not-moved sibling keeps its base identity across the edit, instead of inheriting whichever base identity now happens to sit at its shifted position.

Attributes

Graph
Supertypes
class Object
trait Matchable
class Any
Self type

Members list

Value members

Concrete methods

def matchTrees(base: GenericElement, modified: GenericElement, ignoredAttributes: Set[String] = ...): Matching

Matches every level of base against modified, recursively, starting from their (assumed corresponding) roots.

Matches every level of base against modified, recursively, starting from their (assumed corresponding) roots.

Value parameters

base

the base tree.

ignoredAttributes

attribute names two otherwise-identical subtrees may still differ in and be treated as an exact match anyway (e.g. an auto-stamped timestamp) - same idea, and same default (none), as EditDetector's/ PcsEditDetector's own ignoredAttributes. Applied recursively to every element at every depth, not just the one being compared, since a volatile attribute could sit on any descendant.

modified

one modified version of it.

Attributes

Returns

every matched pair, plus whatever's only in one side.

def matchedLabel(base: GenericElement, modified: GenericElement, ignoredAttributes: Set[String] = ...): NodeRef => String

A label function for modified's nodes, for use with Pcs.relations/PcsEditDetector/ PcsMerger's optional label parameters: whichever base node matchTrees matched a node to gets that base node's own label (Pcs.label) - the same identity Self would have given it, had it one; anything with no match falls back to its own path, prefixed with + so it can never collide with a real base path (which never starts with +).

A label function for modified's nodes, for use with Pcs.relations/PcsEditDetector/ PcsMerger's optional label parameters: whichever base node matchTrees matched a node to gets that base node's own label (Pcs.label) - the same identity Self would have given it, had it one; anything with no match falls back to its own path, prefixed with + so it can never collide with a real base path (which never starts with +).

Attributes