3

I want to build an external GUI that operates on a generic HTML piece that comes with associated CSS. In order to enable some functionalities of the GUI, I would need to create some "meta" HTML elements to contain parts of content and associate them with data.

Example:

<div id="root">
    <foo:meta data-source="document:1111" data-xref="...">
        sometext
        <p class="quote">...</p>
    </foo:meta>
    <p class="other">...</p>
</div>

This HTML is auto-generated starting from already existing HTML that has associated CSS:

<div id="root">
    sometext
    <p class="quote">...</p>
    <p class="other">...</p>
</div>
#root>p {
    color:green;
}
#root>p+p {
    color:red;
}

The problem is, when adding the <foo:meta> element, this breaks CSS child and sibling selectors. I am looking for a way for the CSS selectors to keep working when encapsulating content in this way. We have tried foo\:meta{display:contents} style, but, although it works in terms of hiding the meta element from the box renderer, it doesn't hide it from the selector matcher. We do not produce the HTML/CSS to be processed, so writing them in a certain way before processing is not an option. They come as they are, generic HTML documents with associated CSS.

Is there a way to achieve what we are looking for using HTML/CSS?

To restate, we are looking for a way to dynamically encapsulate parts of content in non-visual elements without breaking child and sibling CSS selectors. The elements should only be available to DOM traversal such as document.getElementsByTagName('foo:meta')

Dinu
  • 1,374
  • 8
  • 21
  • You can use [ShadowDOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) for this, but it requires JavaScript - are you open to using JavaScript? If you're not familiar with it, see the explanation from my answer here https://stackoverflow.com/a/27629265/2756409 – TylerH Feb 18 '19 at 15:08
  • There is no way of introducing elements into the DOM without also introducing them to the CSSOM. AFAIK, The ShadowDOM just constructs a separate DOM/CSSOM, and therefore wouldn't help with something like the `p class="quote"` element which might be targeted by a `#root > .quote` in CSS. I think the point was to not have to change the CSS, @TylerH, so adding `::content` or `::slotted` is out... – Heretic Monkey Feb 18 '19 at 15:14
  • Yes, we can and do use JS, the whole processing is done in the browser. We could add CSS when adding the meta node, as required. We just need to not modify the existing CSS in a way that could break the original display (i.e. changing child selectors do descendant selectors). So any change needs to maintain the original semantic meaning of the CSS, but we can dynamically add to it just like we add to the HTML. – Dinu Feb 18 '19 at 15:19
  • Someone suggested using two comment nodes instead of one DOM content node to mark down the meta range... while this deserves some consideration, as far as I remember the DOM has always been buggy in regard to comments and we'd very much rather achieve this using a semantic DOM content node, if at all possible. – Dinu Feb 18 '19 at 15:35
  • @HereticMonkey CSS styling would not see the ShadowDOM elements as they are encapsulated in their own scope. That seems to be OP's main concern, which is that inserting these elements into the LightDOM is throwing off OP's CSS selection. – TylerH Feb 18 '19 at 15:36
  • @TylerH I am exploring the ShadowDom... I don't know much about it, what I imagine from your reply is this: turn the would-be meta-node's parent into a slotted shadow host, and add the meta-node there. One question pops to mind (without any tests) looking at the expressed scope of the shadow DOM... could CSS rules in either scope cross the shadow boundary? To me it looks like it shouldn't as it would break the encapsulations, so if I'm right doing this would break `.some-ancestor .some-descendant` rules in the original CSS, as `.some-descendant` is moved outside of the outer CSS scope... no? – Dinu Feb 18 '19 at 15:59
  • @Dinu You would insert the meta nodes via JavaScript and they would not appear in the normal (light) DOM, and thus your CSS selectors would not take them into consideration (e.g. :first-child would not see a meta node in order to count it). You *can* style elements in the shadow DOM by using special selectors, like `::slotted`, if you need to. Check out https://developers.google.com/web/fundamentals/web-components/shadowdom for a succinct overview of the featureset. – TylerH Feb 18 '19 at 16:05
  • @TylerH Ok, but here is where I'm left wondering: let's say we have `` DOM, and I turn `b` into a shadow node with a slot for `c`. In this case, would an initial `.a .c {...}` selector still work or would it be broken? – Dinu Feb 18 '19 at 16:12
  • @Dinu If you remove a node from the light DOM and place it in the Shadow DOM, then no, you could not select it normally. So in your example `.a .c {...}` would not work. You could still select it with `.a ::slotted .c {...}` or some variant thereof. – TylerH Feb 18 '19 at 16:22

2 Answers2

0

If I understood your problem correctly.I would suggest using the space between the grandparent and the child instead of a '>'. Also your selector is an id and not a class. The selector you have put in selects the next level child that is the children. But adding the space in between enables you to select grandchildren too! so you have do is this

#root .quote {
    color:green;
}

Let me know if this helped. A working css is here

Yash009
  • 523
  • 4
  • 18
  • Thanks, but the HTML document and attached CSS are generic, out of our production workcycle. We don't write either, we just need to process it. Blindly rewriting child selectors to descendant selectors would break display, and would also not help fix sibling selectors. – Dinu Feb 18 '19 at 14:45
0

So, after much fiddling and research, we came to the conclusion that this can't be done, even with ShadowDom, as even that would require massive CSS rewrites that might not preserve semantics.

However, for anyone stumbling upon this question, we came to the same end by employing the following (I'll be short, pointers only):

  • using two comments to mark where the tag would start/end, instead of an XML tag (eg. <!--<foo:bar data-source="1111">-->...content...<!--</foo:bar>-->)

  • these pointers work more or less like the markup equivalent of a DOM Range and they can work together with it.

  • this approach has the interesting advantage (as opposed to a single node) that it can start and end in different nodes, so it can span subtrees.

  • But this also breaks the XML structure when you try to recompose it. Also it's quite easy by manipulation to end up with the range end moving before the range start, multiple ranges overlapping etc.

  • In order to recompose it (to send to a next XML processor or noSQL XML database for cross-referencing), we need to make sure we avoid the XML-breaking manipulations described above; then, one only needs to convert encapsulated tags to regular tags by using string manipulation on the document (X)HTML (innerHtml, outerHtml, XMLSerializer) to get a clean XML which can be mined and cross-referenced for content.

  • We used the TreeWalker API for document scanning of comments, you might need it, although scanning the document for comments this way can be slow (works for us though). If you are bolder you can try using xPath, ie. document.evaluate('//comment()',document), seems to work but we don't trust all browsers comply.

Dinu
  • 1,374
  • 8
  • 21