UNPKG

rdfa-streaming-parser

Version:
819 lines 40.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RdfaParser = void 0; const htmlparser2_1 = require("htmlparser2"); const readable_stream_1 = require("readable-stream"); const INITIAL_CONTEXT_XHTML = require("./initial-context-xhtml.json"); const INITIAL_CONTEXT = require("./initial-context.json"); const RdfaProfile_1 = require("./RdfaProfile"); const Util_1 = require("./Util"); /** * A stream transformer that parses RDFa (text) streams to an {@link RDF.Stream}. */ class RdfaParser extends readable_stream_1.Transform { constructor(options) { super({ readableObjectMode: true }); this.activeTagStack = []; options = options || {}; this.options = options; this.util = new Util_1.Util(options.dataFactory, options.baseIRI); this.defaultGraph = options.defaultGraph || this.util.dataFactory.defaultGraph(); const profile = options.contentType ? Util_1.Util.contentTypeToProfile(options.contentType) : options.profile || ''; this.features = options.features || RdfaProfile_1.RDFA_FEATURES[profile]; this.htmlParseListener = options.htmlParseListener; this.rdfaPatterns = this.features.copyRdfaPatterns ? {} : null; this.pendingRdfaPatternCopies = this.features.copyRdfaPatterns ? {} : null; this.parser = this.initializeParser(profile === 'xml'); this.activeTagStack.push({ incompleteTriples: [], inlist: false, language: options.language, listMapping: {}, listMappingLocal: {}, name: '', prefixesAll: Object.assign(Object.assign({}, INITIAL_CONTEXT['@context']), this.features.xhtmlInitialContext ? INITIAL_CONTEXT_XHTML['@context'] : {}), prefixesCustom: {}, skipElement: false, vocab: options.vocab, }); } /** * Parses the given text stream into a quad stream. * @param {NodeJS.EventEmitter} stream A text stream. * @return {RDF.Stream} A quad stream. */ import(stream) { const output = new readable_stream_1.PassThrough({ readableObjectMode: true }); stream.on('error', (error) => parsed.emit('error', error)); stream.on('data', (data) => output.push(data)); stream.on('end', () => output.push(null)); const parsed = output.pipe(new RdfaParser(this.options)); return parsed; } _transform(chunk, encoding, callback) { this.parser.write(chunk.toString()); callback(); } _flush(callback) { this.parser.end(); callback(); } onTagOpen(name, attributes) { // Determine the parent tag (ignore skipped tags) let parentTagI = this.activeTagStack.length - 1; while (parentTagI > 0 && this.activeTagStack[parentTagI].skipElement) { parentTagI--; } let parentTag = this.activeTagStack[parentTagI]; // If we skipped a tag, make sure we DO use the lang, prefixes and vocab of the skipped tag if (parentTagI !== this.activeTagStack.length - 1) { parentTag = Object.assign(Object.assign({}, parentTag), { language: this.activeTagStack[this.activeTagStack.length - 1].language, prefixesAll: this.activeTagStack[this.activeTagStack.length - 1].prefixesAll, prefixesCustom: this.activeTagStack[this.activeTagStack.length - 1].prefixesCustom, vocab: this.activeTagStack[this.activeTagStack.length - 1].vocab }); } // Create a new active tag and inherit language scope and baseIRI from parent const activeTag = { collectChildTags: parentTag.collectChildTags, collectChildTagsForCurrentTag: parentTag.collectChildTagsForCurrentTag, incompleteTriples: [], inlist: 'inlist' in attributes, listMapping: [], listMappingLocal: parentTag.listMapping, localBaseIRI: parentTag.localBaseIRI, name, prefixesAll: null, prefixesCustom: null, skipElement: false, }; this.activeTagStack.push(activeTag); // Save the tag contents if needed if (activeTag.collectChildTags) { // Add explicitly defined xmlns, xmlns:* and prefixes to attributes, as required by the spec (Step 11, note) // Sort prefixes alphabetically for deterministic namespace declaration order for (const prefix of Object.keys(parentTag.prefixesCustom).sort()) { const suffix = parentTag.prefixesCustom[prefix]; const attributeKey = prefix === '' ? 'xmlns' : 'xmlns:' + prefix; if (!(attributeKey in attributes)) { attributes[attributeKey] = suffix; } } const attributesSerialized = Object.keys(attributes).map((key) => `${key}="${attributes[key]}"`).join(' '); activeTag.textWithTags = [`<${name}${attributesSerialized ? ' ' + attributesSerialized : ''}>`]; if (this.features.skipHandlingXmlLiteralChildren) { return; } } let allowTermsInRelPredicates = true; let allowTermsInRevPredicates = true; if (this.features.onlyAllowUriRelRevIfProperty) { // Ignore illegal rel/rev values when property is present if ('property' in attributes && 'rel' in attributes) { allowTermsInRelPredicates = false; if (attributes.rel.indexOf(':') < 0) { delete attributes.rel; } } if ('property' in attributes && 'rev' in attributes) { allowTermsInRevPredicates = false; if (attributes.rev.indexOf(':') < 0) { delete attributes.rev; } } } if (this.features.copyRdfaPatterns) { // Save the tag if needed if (parentTag.collectedPatternTag) { const patternTag = { attributes, children: [], name, referenced: false, rootPattern: false, text: [], }; parentTag.collectedPatternTag.children.push(patternTag); activeTag.collectedPatternTag = patternTag; return; } // Store tags with type rdfa:Pattern as patterns if (attributes.typeof === 'rdfa:Pattern') { activeTag.collectedPatternTag = { attributes, children: [], name, parentTag, referenced: false, rootPattern: true, text: [], }; return; } // Instantiate patterns on rdfa:copy if (attributes.property === 'rdfa:copy') { const copyTargetPatternId = attributes.resource || attributes.href || attributes.src; if (this.rdfaPatterns[copyTargetPatternId]) { this.emitPatternCopy(parentTag, this.rdfaPatterns[copyTargetPatternId], copyTargetPatternId); } else { if (!this.pendingRdfaPatternCopies[copyTargetPatternId]) { this.pendingRdfaPatternCopies[copyTargetPatternId] = []; } this.pendingRdfaPatternCopies[copyTargetPatternId].push(parentTag); } return; } } // <base> tags override the baseIRI of the whole document if (this.features.baseTag && name === 'base' && attributes.href) { this.util.baseIRI = this.util.getBaseIRI(attributes.href); } // xml:base attributes override the baseIRI of the current tag and children if (this.features.xmlBase && attributes['xml:base']) { activeTag.localBaseIRI = this.util.getBaseIRI(attributes['xml:base']); } // <time> tags set an initial datatype if (this.features.timeTag && name === 'time' && !attributes.datatype) { activeTag.interpretObjectAsTime = true; } // Processing based on https://www.w3.org/TR/rdfa-core/#s_rdfaindetail // 1: initialize values let newSubject; let currentObjectResource; let typedResource; // 2: handle vocab attribute to set active vocabulary // Vocab sets the active vocabulary if ('vocab' in attributes) { if (attributes.vocab) { activeTag.vocab = attributes.vocab; this.emitTriple(this.util.getBaseIriTerm(activeTag), this.util.dataFactory.namedNode(Util_1.Util.RDFA + 'usesVocabulary'), this.util.dataFactory.namedNode(activeTag.vocab)); } else { // If vocab is set to '', then we fallback to the root vocab as defined via the parser constructor activeTag.vocab = this.activeTagStack[0].vocab; } } else { activeTag.vocab = parentTag.vocab; } // 3: handle prefixes activeTag.prefixesCustom = Util_1.Util.parsePrefixes(attributes, parentTag.prefixesCustom, this.features.xmlnsPrefixMappings); activeTag.prefixesAll = Object.keys(activeTag.prefixesCustom).length > 0 ? Object.assign(Object.assign({}, parentTag.prefixesAll), activeTag.prefixesCustom) : parentTag.prefixesAll; // Handle role attribute if (this.features.roleAttribute && attributes.role) { const roleSubject = attributes.id ? this.util.createIri('#' + attributes.id, activeTag, false, false, false) : this.util.createBlankNode(); // Temporarily override vocab const vocabOld = activeTag.vocab; activeTag.vocab = 'http://www.w3.org/1999/xhtml/vocab#'; for (const role of this.util.createVocabIris(attributes.role, activeTag, true, false)) { this.emitTriple(roleSubject, this.util.dataFactory.namedNode('http://www.w3.org/1999/xhtml/vocab#role'), role); } activeTag.vocab = vocabOld; } // 4: handle language // Save language attribute value in active tag if ('xml:lang' in attributes || (this.features.langAttribute && 'lang' in attributes)) { activeTag.language = attributes['xml:lang'] || attributes.lang; } else { activeTag.language = parentTag.language; } const isRootTag = this.activeTagStack.length === 2; if (!('rel' in attributes) && !('rev' in attributes)) { // 5: Determine the new subject when rel and rev are not present if ('property' in attributes && !('content' in attributes) && !('datatype' in attributes)) { // 5.1: property is present, but not content and datatype // Determine new subject if ('about' in attributes) { newSubject = this.util.createIri(attributes.about, activeTag, false, true, true); activeTag.explicitNewSubject = !!newSubject; } else if (isRootTag) { newSubject = true; } else if (parentTag.object) { newSubject = parentTag.object; } // Determine type if ('typeof' in attributes) { if ('about' in attributes) { typedResource = this.util.createIri(attributes.about, activeTag, false, true, true); } if (!typedResource && isRootTag) { typedResource = true; } if (!typedResource && 'resource' in attributes) { typedResource = this.util.createIri(attributes.resource, activeTag, false, true, true); } if (!typedResource && ('href' in attributes || 'src' in attributes)) { typedResource = this.util.createIri(attributes.href || attributes.src, activeTag, false, false, true); } if (!typedResource && this.isInheritSubjectInHeadBody(name)) { typedResource = newSubject; } if (!typedResource) { typedResource = this.util.createBlankNode(); } currentObjectResource = typedResource; } } else { // 5.2 if ('about' in attributes || 'resource' in attributes) { newSubject = this.util.createIri(attributes.about || attributes.resource, activeTag, false, true, true); activeTag.explicitNewSubject = !!newSubject; } if (!newSubject && ('href' in attributes || 'src' in attributes)) { newSubject = this.util.createIri(attributes.href || attributes.src, activeTag, false, false, true); activeTag.explicitNewSubject = !!newSubject; } if (!newSubject) { if (isRootTag) { newSubject = true; } else if (this.isInheritSubjectInHeadBody(name)) { newSubject = parentTag.object; } else if ('typeof' in attributes) { newSubject = this.util.createBlankNode(); activeTag.explicitNewSubject = true; } else if (parentTag.object) { newSubject = parentTag.object; if (!('property' in attributes)) { activeTag.skipElement = true; } } } // Determine type if ('typeof' in attributes) { typedResource = newSubject; } } } else { // either rel or rev is present // 6: Determine the new subject when rel or rev are present // Define new subject if ('about' in attributes) { newSubject = this.util.createIri(attributes.about, activeTag, false, true, true); activeTag.explicitNewSubject = !!newSubject; if ('typeof' in attributes) { typedResource = newSubject; } } else if (isRootTag) { newSubject = true; } else if (parentTag.object) { newSubject = parentTag.object; } // Define object if ('resource' in attributes) { currentObjectResource = this.util.createIri(attributes.resource, activeTag, false, true, true); } if (!currentObjectResource) { if ('href' in attributes || 'src' in attributes) { currentObjectResource = this.util.createIri(attributes.href || attributes.src, activeTag, false, false, true); } else if ('typeof' in attributes && !('about' in attributes) && !this.isInheritSubjectInHeadBody(name)) { currentObjectResource = this.util.createBlankNode(); } } // Set typed resource if ('typeof' in attributes && !('about' in attributes)) { if (this.isInheritSubjectInHeadBody(name)) { typedResource = newSubject; } else { typedResource = currentObjectResource; } } } // 7: If a typed resource was defined, emit it as a triple if (typedResource) { for (const type of this.util.createVocabIris(attributes.typeof, activeTag, true, true)) { this.emitTriple(this.util.getResourceOrBaseIri(typedResource, activeTag), this.util.dataFactory.namedNode(Util_1.Util.RDF + 'type'), type); } } // 8: Reset list mapping if we have a new subject if (newSubject) { activeTag.listMapping = {}; } // 9: If an object was defined, emit triples for it if (currentObjectResource) { // Handle list mapping if ('rel' in attributes && 'inlist' in attributes) { for (const predicate of this.util.createVocabIris(attributes.rel, activeTag, allowTermsInRelPredicates, false)) { this.addListMapping(activeTag, newSubject, predicate, currentObjectResource); } } // Determine predicates using rel or rev (unless rel and inlist are present) if (!('rel' in attributes && 'inlist' in attributes)) { if ('rel' in attributes) { for (const predicate of this.util.createVocabIris(attributes.rel, activeTag, allowTermsInRelPredicates, false)) { this.emitTriple(this.util.getResourceOrBaseIri(newSubject, activeTag), predicate, this.util.getResourceOrBaseIri(currentObjectResource, activeTag)); } } if ('rev' in attributes) { for (const predicate of this.util.createVocabIris(attributes.rev, activeTag, allowTermsInRevPredicates, false)) { this.emitTriple(this.util.getResourceOrBaseIri(currentObjectResource, activeTag), predicate, this.util.getResourceOrBaseIri(newSubject, activeTag)); } } } } // 10: Store incomplete triples if we don't have an object, but we do have predicates if (!currentObjectResource) { if ('rel' in attributes) { if ('inlist' in attributes) { for (const predicate of this.util.createVocabIris(attributes.rel, activeTag, allowTermsInRelPredicates, false)) { this.addListMapping(activeTag, newSubject, predicate, null); activeTag.incompleteTriples.push({ predicate, reverse: false, list: true }); } } else { for (const predicate of this.util.createVocabIris(attributes.rel, activeTag, allowTermsInRelPredicates, false)) { activeTag.incompleteTriples.push({ predicate, reverse: false }); } } } if ('rev' in attributes) { for (const predicate of this.util.createVocabIris(attributes.rev, activeTag, allowTermsInRevPredicates, false)) { activeTag.incompleteTriples.push({ predicate, reverse: true }); } } // Set a blank node object, so the children can make use of this when completing the triples if (activeTag.incompleteTriples.length > 0) { currentObjectResource = this.util.createBlankNode(); } } // 11: Determine current property value if ('property' in attributes) { // Create predicates activeTag.predicates = this.util.createVocabIris(attributes.property, activeTag, true, false); // Save datatype attribute value in active tag let localObjectResource; if ('datatype' in attributes) { activeTag.datatype = this.util.createIri(attributes.datatype, activeTag, true, true, false); if (activeTag.datatype && (activeTag.datatype.value === Util_1.Util.RDF + 'XMLLiteral' || (this.features.htmlDatatype && activeTag.datatype.value === Util_1.Util.RDF + 'HTML'))) { activeTag.collectChildTags = true; activeTag.collectChildTagsForCurrentTag = true; } } else { // Try to determine resource if (!('rev' in attributes) && !('rel' in attributes) && !('content' in attributes)) { if ('resource' in attributes) { localObjectResource = this.util.createIri(attributes.resource, activeTag, false, true, true); } if (!localObjectResource && 'href' in attributes) { localObjectResource = this.util.createIri(attributes.href, activeTag, false, false, true); } if (!localObjectResource && 'src' in attributes) { localObjectResource = this.util.createIri(attributes.src, activeTag, false, false, true); } } if ('typeof' in attributes && !('about' in attributes)) { localObjectResource = typedResource; } } // If we're in a parent tag that collects child tags, // and we find a tag that does NOT preserve tags, // we mark this tag (and children) to not preserve it. if (!('datatype' in attributes) || attributes.datatype === '') { activeTag.collectChildTagsForCurrentTag = false; } if ('content' in attributes) { // Emit triples based on content attribute has preference over text content const object = this.util.createLiteral(attributes.content, activeTag); if ('inlist' in attributes) { for (const predicate of activeTag.predicates) { this.addListMapping(activeTag, newSubject, predicate, object); } } else { const subject = this.util.getResourceOrBaseIri(newSubject, activeTag); for (const predicate of activeTag.predicates) { this.emitTriple(subject, predicate, object); } } // Unset predicate to avoid text contents to produce new triples activeTag.predicates = null; } else if (this.features.datetimeAttribute && 'datetime' in attributes) { activeTag.interpretObjectAsTime = true; // Datetime attribute on time tag has preference over text content const object = this.util.createLiteral(attributes.datetime, activeTag); if ('inlist' in attributes) { for (const predicate of activeTag.predicates) { this.addListMapping(activeTag, newSubject, predicate, object); } } else { const subject = this.util.getResourceOrBaseIri(newSubject, activeTag); for (const predicate of activeTag.predicates) { this.emitTriple(subject, predicate, object); } } // Unset predicate to avoid text contents to produce new triples activeTag.predicates = null; } else if (localObjectResource) { // Emit triples for all resource objects const object = this.util.getResourceOrBaseIri(localObjectResource, activeTag); if ('inlist' in attributes) { for (const predicate of activeTag.predicates) { this.addListMapping(activeTag, newSubject, predicate, object); } } else { const subject = this.util.getResourceOrBaseIri(newSubject, activeTag); for (const predicate of activeTag.predicates) { this.emitTriple(subject, predicate, object); } } // Unset predicate to avoid text contents to produce new triples activeTag.predicates = null; } } // 12: Complete incomplete triples let incompleteTriplesCompleted = false; if (!activeTag.skipElement && newSubject && parentTag.incompleteTriples.length > 0) { incompleteTriplesCompleted = true; const subject = this.util.getResourceOrBaseIri(parentTag.subject, activeTag); const object = this.util.getResourceOrBaseIri(newSubject, activeTag); for (const incompleteTriple of parentTag.incompleteTriples) { if (!incompleteTriple.reverse) { if (incompleteTriple.list) { // Find the active tag that defined the list by going up the stack let firstInListTag = null; for (let i = this.activeTagStack.length - 1; i >= 0; i--) { if (this.activeTagStack[i].inlist) { firstInListTag = this.activeTagStack[i]; break; } } // firstInListTag is guaranteed to be non-null this.addListMapping(firstInListTag, newSubject, incompleteTriple.predicate, object); } else { this.emitTriple(subject, incompleteTriple.predicate, object); } } else { this.emitTriple(object, incompleteTriple.predicate, subject); } } } if (!incompleteTriplesCompleted && parentTag.incompleteTriples.length > 0) { activeTag.incompleteTriples = activeTag.incompleteTriples.concat(parentTag.incompleteTriples); } // 13: Save evaluation context into active tag activeTag.subject = newSubject || parentTag.subject; activeTag.object = currentObjectResource || newSubject; } onText(data) { const activeTag = this.activeTagStack[this.activeTagStack.length - 1]; // Collect text in pattern tag if needed if (this.features.copyRdfaPatterns && activeTag.collectedPatternTag) { activeTag.collectedPatternTag.text.push(data); return; } // Save the text inside the active tag if (!activeTag.textWithTags) { activeTag.textWithTags = []; } if (!activeTag.textWithoutTags) { activeTag.textWithoutTags = []; } activeTag.textWithTags.push(data); activeTag.textWithoutTags.push(data); } onTagClose() { // Get the active tag const activeTag = this.activeTagStack[this.activeTagStack.length - 1]; const parentTag = this.activeTagStack[this.activeTagStack.length - 2]; if (!(activeTag.collectChildTags && parentTag.collectChildTags && this.features.skipHandlingXmlLiteralChildren)) { // If we detect a finalized rdfa:Pattern tag, store it if (this.features.copyRdfaPatterns && activeTag.collectedPatternTag && activeTag.collectedPatternTag.rootPattern) { const patternId = activeTag.collectedPatternTag.attributes.resource; // Remove resource and typeof attributes to avoid it being seen as a new pattern delete activeTag.collectedPatternTag.attributes.resource; delete activeTag.collectedPatternTag.attributes.typeof; // Store the pattern this.rdfaPatterns[patternId] = activeTag.collectedPatternTag; // Apply all pending copies for this pattern if (this.pendingRdfaPatternCopies[patternId]) { for (const tag of this.pendingRdfaPatternCopies[patternId]) { this.emitPatternCopy(tag, activeTag.collectedPatternTag, patternId); } delete this.pendingRdfaPatternCopies[patternId]; } // Remove the active tag from the stack this.activeTagStack.pop(); return; } // Emit all triples that were determined in the active tag if (activeTag.predicates) { const subject = this.util.getResourceOrBaseIri(activeTag.subject, activeTag); let textSegments; if (!activeTag.collectChildTagsForCurrentTag) { textSegments = activeTag.textWithoutTags || []; } else { textSegments = activeTag.textWithTags || []; if (activeTag.collectChildTags && parentTag.collectChildTags) { // If we are inside an XMLLiteral child that also has RDFa content, ignore the tag name that was collected. textSegments = textSegments.slice(1); } } const object = this.util.createLiteral(textSegments.join(''), activeTag); if (activeTag.inlist) { for (const predicate of activeTag.predicates) { this.addListMapping(activeTag, subject, predicate, object); } } else { for (const predicate of activeTag.predicates) { this.emitTriple(subject, predicate, object); } } // Reset text, unless the parent is also collecting text if (!parentTag.predicates) { activeTag.textWithoutTags = null; activeTag.textWithTags = null; } } // 14: Handle local list mapping if (activeTag.object && Object.keys(activeTag.listMapping).length > 0) { const subject = this.util.getResourceOrBaseIri(activeTag.object, activeTag); for (const predicateValue in activeTag.listMapping) { const predicate = this.util.dataFactory.namedNode(predicateValue); const values = activeTag.listMapping[predicateValue]; if (values.length > 0) { // Non-empty list, emit linked list of rdf:first and rdf:rest chains const bnodes = values.map(() => this.util.createBlankNode()); for (let i = 0; i < values.length; i++) { const object = this.util.getResourceOrBaseIri(values[i], activeTag); this.emitTriple(bnodes[i], this.util.dataFactory.namedNode(Util_1.Util.RDF + 'first'), object); this.emitTriple(bnodes[i], this.util.dataFactory.namedNode(Util_1.Util.RDF + 'rest'), (i < values.length - 1) ? bnodes[i + 1] : this.util.dataFactory.namedNode(Util_1.Util.RDF + 'nil')); } // Emit triple for the first linked list chain this.emitTriple(subject, predicate, bnodes[0]); } else { // Empty list, just emit rdf:nil this.emitTriple(subject, predicate, this.util.dataFactory.namedNode(Util_1.Util.RDF + 'nil')); } } } } // Remove the active tag from the stack this.activeTagStack.pop(); // Save the tag contents if needed if (activeTag.collectChildTags && activeTag.textWithTags) { activeTag.textWithTags.push(`</${activeTag.name}>`); } // If we still have text contents, try to append it to the parent tag if (activeTag.textWithTags && parentTag) { if (!parentTag.textWithTags) { parentTag.textWithTags = activeTag.textWithTags; } else { parentTag.textWithTags = parentTag.textWithTags.concat(activeTag.textWithTags); } } if (activeTag.textWithoutTags && parentTag) { if (!parentTag.textWithoutTags) { parentTag.textWithoutTags = activeTag.textWithoutTags; } else { parentTag.textWithoutTags = parentTag.textWithoutTags.concat(activeTag.textWithoutTags); } } } onEnd() { if (this.features.copyRdfaPatterns) { this.features.copyRdfaPatterns = false; // Emit all unreferenced patterns for (const patternId in this.rdfaPatterns) { const pattern = this.rdfaPatterns[patternId]; if (!pattern.referenced) { pattern.attributes.typeof = 'rdfa:Pattern'; pattern.attributes.resource = patternId; this.emitPatternCopy(pattern.parentTag, pattern, patternId); pattern.referenced = false; delete pattern.attributes.typeof; delete pattern.attributes.resource; } } // Emit all unreferenced copy links for (const patternId in this.pendingRdfaPatternCopies) { for (const parentTag of this.pendingRdfaPatternCopies[patternId]) { this.activeTagStack.push(parentTag); this.onTagOpen('link', { property: 'rdfa:copy', href: patternId }); this.onTagClose(); this.activeTagStack.pop(); } } this.features.copyRdfaPatterns = true; } } /** * If the new subject can be inherited from the parent object * if the resource defines no new subject. * @param {string} name The current tag name. * @returns {boolean} If the subject can be inherited. */ isInheritSubjectInHeadBody(name) { return this.features.inheritSubjectInHeadBody && (name === 'head' || name === 'body'); } /** * Add a list mapping for the given predicate and object in the active tag. * @param {IActiveTag} activeTag The active tag. * @param {Term | boolean} subject A subject term, this will only be used to create a separate list * if activeTag.explicitNewSubject is true. * @param {Term} predicate A predicate term. * @param {Term | boolean} currentObjectResource The current object resource. */ addListMapping(activeTag, subject, predicate, currentObjectResource) { if (activeTag.explicitNewSubject) { const bNode = this.util.createBlankNode(); this.emitTriple(this.util.getResourceOrBaseIri(subject, activeTag), predicate, bNode); this.emitTriple(bNode, this.util.dataFactory.namedNode(Util_1.Util.RDF + 'first'), this.util.getResourceOrBaseIri(currentObjectResource, activeTag)); this.emitTriple(bNode, this.util.dataFactory.namedNode(Util_1.Util.RDF + 'rest'), this.util.dataFactory.namedNode(Util_1.Util.RDF + 'nil')); } else { let predicateList = activeTag.listMappingLocal[predicate.value]; if (!predicateList) { activeTag.listMappingLocal[predicate.value] = predicateList = []; } if (currentObjectResource) { predicateList.push(currentObjectResource); } } } /** * Emit the given triple to the stream. * @param {Term} subject A subject term. * @param {Term} predicate A predicate term. * @param {Term} object An object term. */ emitTriple(subject, predicate, object) { // Validate IRIs if ((subject.termType === 'NamedNode' && subject.value.indexOf(':') < 0) || (predicate.termType === 'NamedNode' && predicate.value.indexOf(':') < 0) || (object.termType === 'NamedNode' && object.value.indexOf(':') < 0)) { return; } this.push(this.util.dataFactory.quad(subject, predicate, object, this.defaultGraph)); } /** * Emit an instantiation of the given pattern with the given parent tag. * @param {IActiveTag} parentTag The parent tag to instantiate in. * @param {IRdfaPattern} pattern The pattern to instantiate. * @param {string} rootPatternId The pattern id. */ emitPatternCopy(parentTag, pattern, rootPatternId) { this.activeTagStack.push(parentTag); pattern.referenced = true; // Ensure that blank nodes within patterns are instantiated only once. // All next pattern copies will reuse the instantiated blank nodes from the first pattern. if (!pattern.constructedBlankNodes) { pattern.constructedBlankNodes = []; this.util.blankNodeFactory = () => { const bNode = this.util.dataFactory.blankNode(); pattern.constructedBlankNodes.push(bNode); return bNode; }; } else { let blankNodeIndex = 0; this.util.blankNodeFactory = () => pattern.constructedBlankNodes[blankNodeIndex++]; } // Apply everything within the pattern this.emitPatternCopyAbsolute(pattern, true, rootPatternId); this.util.blankNodeFactory = null; this.activeTagStack.pop(); } /** * Emit an instantiation of the given pattern with the given parent tag. * * This should probably not be called directly, * call {@link emitPatternCopy} instead. * * @param {IRdfaPattern} pattern The pattern to instantiate. * @param {boolean} root If this is the root call for the given pattern. * @param {string} rootPatternId The pattern id. */ emitPatternCopyAbsolute(pattern, root, rootPatternId) { // Stop on detection of cyclic patterns if (!root && pattern.attributes.property === 'rdfa:copy' && pattern.attributes.href === rootPatternId) { return; } this.onTagOpen(pattern.name, pattern.attributes); for (const text of pattern.text) { this.onText(text); } for (const child of pattern.children) { this.emitPatternCopyAbsolute(child, false, rootPatternId); } this.onTagClose(); } initializeParser(xmlMode) { return new htmlparser2_1.Parser({ onclosetag: () => { try { this.onTagClose(); if (this.htmlParseListener) { this.htmlParseListener.onTagClose(); } } catch (e) { this.emit('error', e); } }, onend: () => { try { this.onEnd(); if (this.htmlParseListener) { this.htmlParseListener.onEnd(); } } catch (e) { this.emit('error', e); } }, onopentag: (name, attributes) => { try { this.onTagOpen(name, attributes); if (this.htmlParseListener) { this.htmlParseListener.onTagOpen(name, attributes); } } catch (e) { this.emit('error', e); } }, ontext: (data) => { try { this.onText(data); if (this.htmlParseListener) { this.htmlParseListener.onText(data); } } catch (e) { this.emit('error', e); } }, }, { decodeEntities: true, recognizeSelfClosing: true, xmlMode, }); } } exports.RdfaParser = RdfaParser; //# sourceMappingURL=RdfaParser.js.map