@@ -9,12 +9,11 @@ import '../../xml/nodes/processing.dart';
99import '../definitions/cardinality.dart' ;
1010import '../definitions/function.dart' ;
1111import '../evaluation/context.dart' ;
12+ import '../exceptions/evaluation_exception.dart' ;
1213import '../types/node.dart' ;
1314import '../types/sequence.dart' ;
1415import '../types/string.dart' ;
1516
16- Object ? _defaultToContextItem (XPathContext context) => context.item;
17-
1817/// https://www.w3.org/TR/xpath-functions-31/#func-name
1918const fnName = XPathFunctionDefinition (
2019 name: 'fn:name' ,
@@ -105,12 +104,30 @@ const fnId = XPathFunctionDefinition(
105104 cardinality: XPathCardinality .zeroOrMore,
106105 ),
107106 ],
108- optionalArguments: [XPathArgumentDefinition (name: 'node' , type: xsNode)],
107+ optionalArguments: [
108+ XPathArgumentDefinition (
109+ name: 'node' ,
110+ type: xsNode,
111+ cardinality: XPathCardinality .zeroOrOne,
112+ defaultValue: _defaultToContextItem,
113+ ),
114+ ],
109115 function: _fnId,
110116);
111117
112118XPathSequence _fnId (XPathContext context, XPathSequence arg, [XmlNode ? node]) {
113- throw UnimplementedError ('fn:id' );
119+ final ids = _parseIdStrings (arg);
120+ if (ids.isEmpty) return XPathSequence .empty;
121+ final root = node? .root;
122+ if (root == null ) throw XPathEvaluationException ('Invalid document' );
123+ return XPathSequence (
124+ root.descendantElements.where (
125+ (element) => element.attributes.any (
126+ (attribute) =>
127+ _isIdAttribute (attribute) && ids.contains (attribute.value.trim ()),
128+ ),
129+ ),
130+ );
114131}
115132
116133/// https://www.w3.org/TR/xpath-functions-31/#func-element-with-id
@@ -124,7 +141,14 @@ const fnElementWithId = XPathFunctionDefinition(
124141 cardinality: XPathCardinality .zeroOrMore,
125142 ),
126143 ],
127- optionalArguments: [XPathArgumentDefinition (name: 'node' , type: xsNode)],
144+ optionalArguments: [
145+ XPathArgumentDefinition (
146+ name: 'node' ,
147+ type: xsNode,
148+ cardinality: XPathCardinality .zeroOrOne,
149+ defaultValue: _defaultToContextItem,
150+ ),
151+ ],
128152 function: _fnElementWithId,
129153);
130154
@@ -133,7 +157,19 @@ XPathSequence _fnElementWithId(
133157 XPathSequence arg, [
134158 XmlNode ? node,
135159]) {
136- throw UnimplementedError ('fn:element-with-id' );
160+ final ids = _parseIdStrings (arg);
161+ if (ids.isEmpty) return XPathSequence .empty;
162+ final root = node? .root;
163+ if (root == null ) throw XPathEvaluationException ('Invalid document' );
164+ final seen = < String > {};
165+ return XPathSequence (
166+ root.descendantElements.where (
167+ (element) => element.attributes.where (_isIdAttribute).any ((attribute) {
168+ final value = attribute.value.trim ();
169+ return ids.contains (value) && seen.add (value);
170+ }),
171+ ),
172+ );
137173}
138174
139175/// https://www.w3.org/TR/xpath-functions-31/#func-idref
@@ -147,7 +183,14 @@ const fnIdref = XPathFunctionDefinition(
147183 cardinality: XPathCardinality .zeroOrMore,
148184 ),
149185 ],
150- optionalArguments: [XPathArgumentDefinition (name: 'node' , type: xsNode)],
186+ optionalArguments: [
187+ XPathArgumentDefinition (
188+ name: 'node' ,
189+ type: xsNode,
190+ cardinality: XPathCardinality .zeroOrOne,
191+ defaultValue: _defaultToContextItem,
192+ ),
193+ ],
151194 function: _fnIdref,
152195);
153196
@@ -156,7 +199,19 @@ XPathSequence _fnIdref(
156199 XPathSequence arg, [
157200 XmlNode ? node,
158201]) {
159- throw UnimplementedError ('fn:idref' );
202+ final ids = _parseIdStrings (arg);
203+ if (ids.isEmpty) return XPathSequence .empty;
204+ final root = node? .root;
205+ if (root == null ) throw XPathEvaluationException ('Invalid document' );
206+ return XPathSequence (
207+ root.descendantElements.expand (
208+ (element) => element.attributes.where (
209+ (attribute) =>
210+ _isIdrefAttribute (attribute) &&
211+ attribute.value.trim ().split (_whitespace).any (ids.contains),
212+ ),
213+ ),
214+ );
160215}
161216
162217/// https://www.w3.org/TR/xpath-functions-31/#func-generate-id
@@ -318,3 +373,23 @@ XPathSequence _fnPath(XPathContext context, [XmlNode? node]) {
318373 }
319374 return XPathSequence .single (components.reversed.join ('/' ));
320375}
376+
377+ final _whitespace = RegExp (r'\s+' );
378+
379+ XmlNode _defaultToContextItem (XPathContext context) =>
380+ xsNode.cast (context.item);
381+
382+ Set <String > _parseIdStrings (XPathSequence arg) => arg
383+ .map (xsString.cast)
384+ .expand ((each) => each.split (_whitespace))
385+ .where ((each) => each.isNotEmpty)
386+ .toSet ();
387+
388+ bool _isIdAttribute (XmlAttribute attr) =>
389+ attr.name.local == 'id' || attr.name.qualified == 'xml:id' ;
390+
391+ bool _isIdrefAttribute (XmlAttribute attr) =>
392+ attr.name.local == 'idref' ||
393+ attr.name.local == 'idrefs' ||
394+ attr.name.qualified == 'xml:idref' ||
395+ attr.name.qualified == 'xml:idrefs' ;
0 commit comments